├── .github ├── scripts │ ├── check_build_result.sh │ ├── check_leak.sh │ └── release_rollback.sh └── workflows │ ├── ci-build.yml │ ├── ci-deploy.yml │ ├── ci-pr-reports.yml │ ├── ci-pr.yml │ ├── ci-release.yml │ └── codeql-analysis.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── docker ├── Dockerfile.centos7 ├── README.md ├── docker-compose.centos-7.18.yaml └── docker-compose.centos-7.yaml ├── license └── LICENSE.mvn-wrapper.txt ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main └── java │ └── io │ └── netty │ └── incubator │ └── codec │ └── http3 │ ├── CharSequenceMap.java │ ├── DefaultHttp3CancelPushFrame.java │ ├── DefaultHttp3DataFrame.java │ ├── DefaultHttp3GoAwayFrame.java │ ├── DefaultHttp3Headers.java │ ├── DefaultHttp3HeadersFrame.java │ ├── DefaultHttp3MaxPushIdFrame.java │ ├── DefaultHttp3PushPromiseFrame.java │ ├── DefaultHttp3SettingsFrame.java │ ├── DefaultHttp3UnknownFrame.java │ ├── Http3.java │ ├── Http3CancelPushFrame.java │ ├── Http3ClientConnectionHandler.java │ ├── Http3CodecUtils.java │ ├── Http3ConnectionHandler.java │ ├── Http3ControlStreamFrame.java │ ├── Http3ControlStreamFrameTypeValidator.java │ ├── Http3ControlStreamInboundHandler.java │ ├── Http3ControlStreamOutboundHandler.java │ ├── Http3DataFrame.java │ ├── Http3ErrorCode.java │ ├── Http3Exception.java │ ├── Http3Frame.java │ ├── Http3FrameCodec.java │ ├── Http3FrameToHttpObjectCodec.java │ ├── Http3FrameTypeDuplexValidationHandler.java │ ├── Http3FrameTypeInboundValidationHandler.java │ ├── Http3FrameTypeOutboundValidationHandler.java │ ├── Http3FrameTypeValidator.java │ ├── Http3FrameValidationUtils.java │ ├── Http3GoAwayFrame.java │ ├── Http3Headers.java │ ├── Http3HeadersFrame.java │ ├── Http3HeadersSink.java │ ├── Http3HeadersValidationException.java │ ├── Http3MaxPushIdFrame.java │ ├── Http3PushPromiseFrame.java │ ├── Http3PushStreamClientInitializer.java │ ├── Http3PushStreamClientValidationHandler.java │ ├── Http3PushStreamFrame.java │ ├── Http3PushStreamFrameTypeValidator.java │ ├── Http3PushStreamServerInitializer.java │ ├── Http3PushStreamServerValidationHandler.java │ ├── Http3RequestStreamCodecState.java │ ├── Http3RequestStreamDecodeStateValidator.java │ ├── Http3RequestStreamEncodeStateValidator.java │ ├── Http3RequestStreamFrame.java │ ├── Http3RequestStreamFrameTypeValidator.java │ ├── Http3RequestStreamInboundHandler.java │ ├── Http3RequestStreamInitializer.java │ ├── Http3RequestStreamValidationHandler.java │ ├── Http3RequestStreamValidationUtils.java │ ├── Http3ServerConnectionHandler.java │ ├── Http3ServerPushStreamManager.java │ ├── Http3SettingsFrame.java │ ├── Http3UnidirectionalStreamInboundClientHandler.java │ ├── Http3UnidirectionalStreamInboundHandler.java │ ├── Http3UnidirectionalStreamInboundServerHandler.java │ ├── Http3UnknownFrame.java │ ├── HttpConversionUtil.java │ ├── NotNullByDefault.java │ ├── QpackAttributes.java │ ├── QpackDecoder.java │ ├── QpackDecoderDynamicTable.java │ ├── QpackDecoderHandler.java │ ├── QpackDecoderStateSyncStrategy.java │ ├── QpackEncoder.java │ ├── QpackEncoderDynamicTable.java │ ├── QpackEncoderHandler.java │ ├── QpackException.java │ ├── QpackHeaderField.java │ ├── QpackHuffmanDecoder.java │ ├── QpackHuffmanEncoder.java │ ├── QpackStaticTable.java │ ├── QpackUtil.java │ └── package-info.java └── test ├── java └── io │ └── netty │ └── incubator │ └── codec │ └── http3 │ ├── AbstractHttp3FrameTypeValidationHandlerTest.java │ ├── AbtractHttp3ConnectionHandlerTest.java │ ├── EmbeddedQuicChannel.java │ ├── EmbeddedQuicStreamChannel.java │ ├── Http3ClientConnectionHandlerTest.java │ ├── Http3ControlStreamFrameTypeValidatorTest.java │ ├── Http3ControlStreamInboundHandlerTest.java │ ├── Http3ControlStreamOutboundHandlerTest.java │ ├── Http3FrameCodecTest.java │ ├── Http3FrameToHttpObjectCodecTest.java │ ├── Http3FrameTypeValidationHandlerTest.java │ ├── Http3FrameTypeValidatorTest.java │ ├── Http3HeadersSinkTest.java │ ├── Http3PushStreamFrameTypeValidatorTest.java │ ├── Http3PushStreamServerValidationHandlerTest.java │ ├── Http3PushStreamTest.java │ ├── Http3RequestStreamFrameTypeValidatorTest.java │ ├── Http3RequestStreamInboundHandlerTest.java │ ├── Http3RequestStreamValidationHandlerTest.java │ ├── Http3ServerConnectionHandlerTest.java │ ├── Http3ServerPushStreamManagerTest.java │ ├── Http3SpecTestServer.java │ ├── Http3TestUtils.java │ ├── Http3UnidirectionalStreamInboundHandlerTest.java │ ├── HttpConversionUtilTest.java │ ├── QpackDecoderDynamicTableTest.java │ ├── QpackDecoderHandlerTest.java │ ├── QpackDecoderTest.java │ ├── QpackEncoderDecoderTest.java │ ├── QpackEncoderDynamicTableTest.java │ ├── QpackStaticTableTest.java │ ├── QpackStreamHandlerTest.java │ └── example │ ├── Http3ClientExample.java │ └── Http3ServerExample.java └── resources ├── cert.crt └── cert.key /.github/scripts/check_build_result.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------- 3 | # Copyright 2021 The Netty Project 4 | # 5 | # The Netty Project licenses this file to you under the Apache License, 6 | # version 2.0 (the "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at: 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | # ---------------------------------------------------------------------------- 17 | set -e 18 | 19 | if [ "$#" -ne 1 ]; then 20 | echo "Expected build log as argument" 21 | exit 1 22 | fi 23 | 24 | if grep -q 'BUILD FAILURE' $1 ; then 25 | echo "Build failure detected, please inspect build log" 26 | exit 1 27 | else 28 | echo "Build successful" 29 | exit 0 30 | fi 31 | -------------------------------------------------------------------------------- /.github/scripts/check_leak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------- 3 | # Copyright 2021 The Netty Project 4 | # 5 | # The Netty Project licenses this file to you under the Apache License, 6 | # version 2.0 (the "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at: 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | # ---------------------------------------------------------------------------- 17 | set -e 18 | 19 | if [ "$#" -ne 1 ]; then 20 | echo "Expected build log as argument" 21 | exit 1 22 | fi 23 | 24 | if grep -q 'LEAK:' $1 ; then 25 | echo "Leak detected, please inspect build log" 26 | exit 1 27 | else 28 | echo "No Leak detected" 29 | exit 0 30 | fi 31 | 32 | -------------------------------------------------------------------------------- /.github/scripts/release_rollback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------- 3 | # Copyright 2021 The Netty Project 4 | # 5 | # The Netty Project licenses this file to you under the Apache License, 6 | # version 2.0 (the "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at: 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | # ---------------------------------------------------------------------------- 17 | set -e 18 | 19 | if [ "$#" -ne 1 ]; then 20 | echo "Expected release.properties file" 21 | exit 1 22 | fi 23 | 24 | TAG=$(grep scm.tag= "$1" | cut -d'=' -f2) 25 | ./mvnw -B --file pom.xml release:rollback 26 | git push origin :"$TAG" 27 | -------------------------------------------------------------------------------- /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: Build project 17 | 18 | on: 19 | push: 20 | branches: [ main ] 21 | 22 | schedule: 23 | - cron: '30 8 * * 1' # At 08:30 on Monday, every Monday. 24 | 25 | # Allows you to run this workflow manually from the Actions tab 26 | workflow_dispatch: 27 | 28 | env: 29 | MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryhandler.count=5 -Dmaven.wagon.httpconnectionManager.ttlSeconds=240 30 | 31 | # Cancel running jobs when a new push happens to the same branch as otherwise it will 32 | # tie up too many resources without providing much value. 33 | concurrency: 34 | group: ${{ github.workflow }}-${{ github.ref }} 35 | cancel-in-progress: true 36 | 37 | jobs: 38 | build: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | # Cache .m2/repository 44 | - uses: actions/cache@v4 45 | continue-on-error: true 46 | with: 47 | path: ~/.m2/repository 48 | key: build-cache-m2-repository-${{ hashFiles('**/pom.xml') }} 49 | restore-keys: | 50 | build-cache-m2-repository- 51 | 52 | - name: Build docker image 53 | run: docker compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml build 54 | 55 | - name: Execute project build without leak detection 56 | run: docker compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml run build | tee build-leak.output 57 | 58 | - name: Checking for test failures 59 | run: ./.github/scripts/check_build_result.sh build-leak.output 60 | 61 | - uses: actions/upload-artifact@v4 62 | if: ${{ failure() }} 63 | with: 64 | name: target 65 | path: | 66 | **/target/surefire-reports/ 67 | **/hs_err*.log 68 | -------------------------------------------------------------------------------- /.github/workflows/ci-deploy.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: Deploy project 17 | 18 | on: 19 | push: 20 | branches: [ main ] 21 | 22 | schedule: 23 | - cron: '30 8 * * 1' # At 08:30 on Monday, every Monday. 24 | 25 | # Allows you to run this workflow manually from the Actions tab 26 | workflow_dispatch: 27 | 28 | env: 29 | MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryhandler.count=5 -Dmaven.wagon.httpconnectionManager.ttlSeconds=240 30 | 31 | # Cancel running jobs when a new push happens to the same branch as otherwise it will 32 | # tie up too many resources without providing much value. 33 | concurrency: 34 | group: ${{ github.workflow }}-${{ github.ref }} 35 | cancel-in-progress: true 36 | 37 | jobs: 38 | deploy: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: s4u/maven-settings-action@v3.0.0 42 | with: 43 | servers: | 44 | [{ 45 | "id": "central", 46 | "username": "${{ secrets.MAVEN_CENTRAL_USERNAME }}", 47 | "password": "${{ secrets.MAVEN_CENTRAL_PASSWORD }}" 48 | }] 49 | 50 | - uses: actions/checkout@v4 51 | 52 | # Cache .m2/repository 53 | - uses: actions/cache@v4 54 | continue-on-error: true 55 | with: 56 | path: ~/.m2/repository 57 | key: deploy-cache-m2-repository-${{ hashFiles('**/pom.xml') }} 58 | restore-keys: | 59 | deploy-cache-m2-repository- 60 | 61 | - name: Build docker image 62 | run: docker compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml build 63 | 64 | - name: Deploy project snapshot to sonatype 65 | run: docker compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml run deploy -------------------------------------------------------------------------------- /.github/workflows/ci-pr-reports.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: PR Reports 17 | on: 18 | workflow_run: 19 | workflows: [ "Build PR" ] 20 | types: 21 | - completed 22 | 23 | env: 24 | MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryhandler.count=5 -Dmaven.wagon.httpconnectionManager.ttlSeconds=240 25 | 26 | # Cancel running jobs when a new push happens to the same branch as otherwise it will 27 | # tie up too many resources without providing much value. 28 | concurrency: 29 | group: ${{ github.workflow }}-${{ github.ref }} 30 | cancel-in-progress: true 31 | 32 | jobs: 33 | tests: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Download Artifacts 37 | uses: dawidd6/action-download-artifact@v6 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | workflow: ${{ github.event.workflow_run.workflow_id }} 41 | workflow_conclusion: completed 42 | commit: ${{ github.event.workflow_run.head_commit.id }} 43 | # File location set in ci-pr.yml and must be coordinated. 44 | name: test-results-build 45 | - name: Publish Test Report 46 | uses: scacap/action-surefire-report@v1.7.2 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} 49 | report_paths: '**/target/surefire-reports/TEST-*.xml' 50 | commit: ${{ github.event.workflow_run.head_commit.id }} 51 | check_name: test reports 52 | -------------------------------------------------------------------------------- /.github/workflows/ci-pr.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: Build PR 17 | 18 | on: 19 | pull_request: 20 | branches: [ main ] 21 | 22 | # Allows you to run this workflow manually from the Actions tab 23 | workflow_dispatch: 24 | 25 | env: 26 | MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryhandler.count=5 -Dmaven.wagon.httpconnectionManager.ttlSeconds=240 27 | 28 | # Cancel running jobs when a new push happens to the same branch as otherwise it will 29 | # tie up too many resources without providing much value. 30 | concurrency: 31 | group: ${{ github.workflow }}-${{ github.ref }} 32 | cancel-in-progress: true 33 | 34 | jobs: 35 | verify: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Set up JDK 8 40 | uses: actions/setup-java@v4 41 | with: 42 | java-version: 8 43 | distribution: 'zulu' 44 | # Cache .m2/repository 45 | - uses: actions/cache@v4 46 | continue-on-error: true 47 | with: 48 | path: ~/.m2/repository 49 | key: verify-cache-m2-repository-${{ hashFiles('**/pom.xml') }} 50 | restore-keys: | 51 | verify-cache-m2-repository- 52 | - name: Verify with Maven 53 | run: ./mvnw -B --file pom.xml verify -DskipTests=true -DskipH3Spec=true 54 | 55 | build: 56 | runs-on: ubuntu-latest 57 | needs: verify 58 | steps: 59 | - uses: actions/checkout@v4 60 | 61 | # Cache .m2/repository 62 | - uses: actions/cache@v4 63 | continue-on-error: true 64 | with: 65 | path: ~/.m2/repository 66 | key: pr-cache-m2-repository-${{ hashFiles('**/pom.xml') }} 67 | restore-keys: | 68 | pr-cache-m2-repository- 69 | 70 | - name: Build docker image 71 | run: docker compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml build 72 | 73 | - name: Execute project build with leak detection 74 | run: docker compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml run build-leak | tee build-leak.output 75 | 76 | - name: Checking for test failures 77 | run: ./.github/scripts/check_build_result.sh build-leak.output 78 | 79 | - name: Checking for detected leak 80 | run: ./.github/scripts/check_leak.sh build-leak.output 81 | 82 | - name: Upload Test Results 83 | if: always() 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: test-results-build 87 | path: '**/target/surefire-reports/TEST-*.xml' 88 | 89 | - uses: actions/upload-artifact@v4 90 | if: ${{ failure() }} 91 | with: 92 | name: target 93 | path: | 94 | **/target/surefire-reports/ 95 | **/hs_err*.log 96 | -------------------------------------------------------------------------------- /.github/workflows/ci-release.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: Release project 17 | 18 | on: 19 | # Allows you to run this workflow manually from the Actions tab 20 | workflow_dispatch: 21 | 22 | env: 23 | MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryhandler.count=5 -Dmaven.wagon.httpconnectionManager.ttlSeconds=240 24 | 25 | # Cancel running jobs when a new push happens to the same branch as otherwise it will 26 | # tie up too many resources without providing much value. 27 | concurrency: 28 | group: ${{ github.workflow }}-${{ github.ref }} 29 | cancel-in-progress: true 30 | 31 | jobs: 32 | release: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | with: 37 | ref: main 38 | 39 | - name: Set up JDK 8 40 | uses: actions/setup-java@v4 41 | with: 42 | java-version: 8 43 | distribution: 'zulu' 44 | 45 | - name: Import GPG key 46 | id: import_gpg 47 | uses: crazy-max/ghaction-import-gpg@v6 48 | with: 49 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 50 | passphrase: ${{ secrets.GPG_PASSPHRASE }} 51 | 52 | - name: Setup git configuration 53 | run: | 54 | git config --global user.email "netty-project-bot@users.noreply.github.com" 55 | git config --global user.name "Netty Project Bot" 56 | 57 | - name: Install SSH key 58 | uses: shimataro/ssh-key-action@v2 59 | with: 60 | key: ${{ secrets.SSH_PRIVATE_KEY_PEM }} 61 | known_hosts: ${{ secrets.SSH_KNOWN_HOSTS }} 62 | 63 | - uses: s4u/maven-settings-action@v3.0.0 64 | with: 65 | servers: | 66 | [{ 67 | "id": "central", 68 | "username": "${{ secrets.MAVEN_CENTRAL_USERNAME }}", 69 | "password": "${{ secrets.MAVEN_CENTRAL_PASSWORD }}" 70 | }] 71 | 72 | # Cache .m2/repository 73 | - uses: actions/cache@v4 74 | continue-on-error: true 75 | with: 76 | path: ~/.m2/repository 77 | key: release-cache-m2-repository-${{ hashFiles('**/pom.xml') }} 78 | restore-keys: | 79 | release-cache-m2-repository- 80 | 81 | - name: Prepare release with Maven 82 | run: ./mvnw -B --file pom.xml release:prepare -DpreparationGoals=clean -DskipTests=true -DskipH3Spec=true 83 | 84 | - name: Perform release with Maven 85 | run: ./mvnw -B --file pom.xml release:perform -Drelease.gpg.keyname=${{ secrets.GPG_KEYNAME }} -Drelease.gpg.passphrase=${{ secrets.GPG_PASSPHRASE }} -DstagingProgressTimeoutMinutes=10 86 | 87 | - name: Rollback release on failure 88 | if: ${{ failure() }} 89 | # Rollback the release in case of an failure 90 | run: bash ./.github/scripts/release_rollback.sh release.properties 91 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: "CodeQL" 17 | 18 | on: 19 | push: 20 | branches: [ main ] 21 | pull_request: 22 | # The branches below must be a subset of the branches above 23 | branches: [ main ] 24 | schedule: 25 | - cron: '40 9 * * 1' 26 | 27 | env: 28 | MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryhandler.count=5 -Dmaven.wagon.httpconnectionManager.ttlSeconds=240 29 | 30 | # Cancel running jobs when a new push happens to the same branch as otherwise it will 31 | # tie up too many resources without providing much value. 32 | concurrency: 33 | group: ${{ github.workflow }}-${{ github.ref }} 34 | cancel-in-progress: true 35 | 36 | jobs: 37 | analyze: 38 | name: Analyze 39 | runs-on: ubuntu-latest 40 | 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | language: [ 'java' ] 45 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 46 | # Learn more... 47 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 48 | 49 | steps: 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | # Cache .m2/repository 54 | - uses: actions/cache@v4 55 | continue-on-error: true 56 | with: 57 | path: ~/.m2/repository 58 | key: ${{ matrix.language }}-cache-m2-repository-${{ hashFiles('**/pom.xml') }} 59 | restore-keys: | 60 | ${{ matrix.language }}-cache-m2-repository- 61 | 62 | # Initializes the CodeQL tools for scanning. 63 | - name: Initialize CodeQL 64 | uses: github/codeql-action/init@v2 65 | with: 66 | languages: ${{ matrix.language }} 67 | # If you wish to specify custom queries, you can do so here or in a config file. 68 | # By default, queries listed here will override any specified in a config file. 69 | # Prefix the list here with "+" to use these queries and those in the config file. 70 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 71 | 72 | - name: Build project 73 | run: ./mvnw clean package -DskipTests=true -DskipH3Spec=true 74 | 75 | - name: Perform CodeQL Analysis 76 | uses: github/codeql-action/analyze@v2 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .project 3 | .classpath 4 | .settings 5 | 6 | # IntelliJ IDEA project files and directories 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea/ 11 | .shelf/ 12 | 13 | # Geany project file 14 | .geany 15 | 16 | # KDevelop project file and directory 17 | .kdev4/ 18 | *.kdev4 19 | 20 | # Build targets 21 | /target 22 | */target 23 | 24 | # Report directories 25 | /reports 26 | */reports 27 | 28 | # Mac-specific directory that no other operating system needs. 29 | .DS_Store 30 | 31 | # JVM crash logs 32 | hs_err_pid*.log 33 | 34 | dependency-reduced-pom.xml 35 | 36 | */.unison.* 37 | 38 | # exclude mainframer files 39 | mainframer 40 | .mainframer 41 | 42 | # exclude docker-sync stuff 43 | .docker-sync 44 | */.docker-sync 45 | 46 | # exclude vscode files 47 | .vscode/ 48 | *.factorypath 49 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netty/netty-incubator-codec-http3/5adde7c9d29c9198aa633eacd8c500f5a6f3bdfd/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://maven-central.storage-download.googleapis.com/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | distributionSha512Sum=0eb0432004a91ebf399314ad33e5aaffec3d3b29279f2f143b2f43ade26f4db7bd1c0f08e436e9445ac6dc4a564a2945d13072a160ae54a930e90581284d6461 19 | 20 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | The Netty Project 3 | ================= 4 | 5 | Please visit the Netty web site for more information: 6 | 7 | * https://netty.io/ 8 | 9 | Copyright 2020 The Netty Project 10 | 11 | The Netty Project licenses this file to you under the Apache License, 12 | version 2.0 (the "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at: 14 | 15 | https://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 20 | License for the specific language governing permissions and limitations 21 | under the License. 22 | 23 | Also, please refer to each LICENSE..txt file, which is located in 24 | the 'license' directory of the distribution file, for the license terms of the 25 | components that this product depends on. 26 | 27 | ------------------------------------------------------------------------------- 28 | 29 | This product contains the Maven wrapper scripts from 'Maven Wrapper', that provides an easy way to ensure a user has everything necessary to run the Maven build. 30 | 31 | * LICENSE: 32 | * license/LICENSE.mvn-wrapper.txt (Apache License 2.0) 33 | * HOMEPAGE: 34 | * https://github.com/takari/maven-wrapper 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build project](https://github.com/netty/netty-incubator-codec-http3/workflows/Build%20project/badge.svg) 2 | 3 | # netty-incubator-codec-http3 4 | Experimental HTTP3 codec on top of our own [QUIC codec](https://github.com/netty/netty-incubator-codec-quic). 5 | 6 | ## How Can I use it ? 7 | 8 | For some example usage please checkout our 9 | [server example](https://github.com/netty/netty-incubator-codec-http3/blob/main/src/test/java/io/netty/incubator/codec/http3/example/Http3ServerExample.java) and 10 | [client example](https://github.com/netty/netty-incubator-codec-http3/blob/main/src/test/java/io/netty/incubator/codec/http3/example/Http3ClientExample.java). 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.centos7: -------------------------------------------------------------------------------- 1 | FROM centos:7.6.1810 2 | 3 | ENV SOURCE_DIR /root/source 4 | 5 | RUN sed -i -e 's/^mirrorlist/#mirrorlist/g' -e 's/^#baseurl=http:\/\/mirror.centos.org\/centos\/$releasever\//baseurl=https:\/\/vault.centos.org\/\/7.6.1810\//g' /etc/yum.repos.d/CentOS-Base.repo 6 | 7 | # install dependencies 8 | RUN yum install -y \ 9 | bzip2 \ 10 | git \ 11 | gnupg \ 12 | tar \ 13 | unzip \ 14 | wget \ 15 | zip 16 | 17 | RUN mkdir $SOURCE_DIR 18 | WORKDIR $SOURCE_DIR 19 | 20 | # Downloading and installing SDKMAN! 21 | RUN curl -s "https://get.sdkman.io" | bash 22 | 23 | ARG java_version="8.0.402-zulu" 24 | ENV JAVA_VERSION $java_version 25 | 26 | # Installing Java removing some unnecessary SDKMAN files 27 | RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && \ 28 | yes | sdk install java $JAVA_VERSION && \ 29 | rm -rf $HOME/.sdkman/archives/* && \ 30 | rm -rf $HOME/.sdkman/tmp/*" 31 | 32 | RUN echo 'export JAVA_HOME="/root/.sdkman/candidates/java/current"' >> ~/.bashrc 33 | RUN echo 'PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Using the docker images 2 | 3 | ``` 4 | cd /path/to/source/ 5 | ``` 6 | 7 | ## centos 7 with java 8 8 | 9 | ``` 10 | docker-compose -f docker/docker-compose.centos-7.yaml -f docker/docker-compose.centos-7.18.yaml run build 11 | ``` 12 | -------------------------------------------------------------------------------- /docker/docker-compose.centos-7.18.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | runtime-setup: 6 | image: netty-codec-http3-centos7:centos-7-1.8 7 | build: 8 | args: 9 | java_version : "8.0.422-zulu" 10 | 11 | build: 12 | image: netty-codec-http3-centos7:centos-7-1.8 13 | 14 | build-leak: 15 | image: netty-codec-http3-centos7:centos-7-1.8 16 | 17 | deploy: 18 | image: netty-codec-http3-centos7:centos-7-1.8 19 | 20 | shell: 21 | image: netty-codec-http3-centos7:centos-7-1.8 22 | -------------------------------------------------------------------------------- /docker/docker-compose.centos-7.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | runtime-setup: 6 | image: netty-codec-http3-centos7:default 7 | build: 8 | context: ../ 9 | dockerfile: docker/Dockerfile.centos7 10 | 11 | common: &common 12 | image: netty-codec-http3-centos7:default 13 | depends_on: [runtime-setup] 14 | environment: 15 | - GPG_KEYNAME 16 | - GPG_PASSPHRASE 17 | - GPG_PRIVATE_KEY 18 | - MAVEN_OPTS 19 | volumes: 20 | - ~/.ssh:/root/.ssh:delegated 21 | - ~/.gnupg:/root/.gnupg:delegated 22 | - ~/.m2/repository:/root/.m2/repository 23 | - ..:/code:delegated 24 | working_dir: /code 25 | 26 | build: 27 | <<: *common 28 | command: /bin/bash -cl "./mvnw clean package" 29 | 30 | build-leak: 31 | <<: *common 32 | command: /bin/bash -cl "./mvnw -Pleak clean package" 33 | 34 | deploy: 35 | <<: *common 36 | command: /bin/bash -cl "./mvnw clean deploy -DskipTests=true" 37 | volumes: 38 | - ~/.ssh:/root/.ssh 39 | - ~/.gnupg:/root/.gnupg 40 | - ~/.m2/repository:/root/.m2/repository 41 | - ~/.m2/settings.xml:/root/.m2/settings.xml 42 | - ..:/code 43 | 44 | shell: 45 | <<: *common 46 | volumes: 47 | - ~/.ssh:/root/.ssh:delegated 48 | - ~/.gnupg:/root/.gnupg:delegated 49 | - ~/.m2:/root/.m2:delegated 50 | - ~/.gitconfig:/root/.gitconfig:delegated 51 | - ~/.gitignore:/root/.gitignore:delegated 52 | - ..:/code:delegated 53 | entrypoint: /bin/bash 54 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/CharSequenceMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.handler.codec.DefaultHeaders; 19 | import io.netty.handler.codec.UnsupportedValueConverter; 20 | import io.netty.handler.codec.ValueConverter; 21 | 22 | import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; 23 | import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; 24 | 25 | /** 26 | * Internal use only! 27 | */ 28 | final class CharSequenceMap extends DefaultHeaders> { 29 | CharSequenceMap() { 30 | this(true); 31 | } 32 | 33 | CharSequenceMap(boolean caseSensitive) { 34 | this(caseSensitive, UnsupportedValueConverter.instance()); 35 | } 36 | 37 | CharSequenceMap(boolean caseSensitive, ValueConverter valueConverter) { 38 | super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter); 39 | } 40 | 41 | @SuppressWarnings("unchecked") 42 | CharSequenceMap(boolean caseSensitive, ValueConverter valueConverter, int arraySizeHint) { 43 | super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter, 44 | NameValidator.NOT_NULL, arraySizeHint); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3CancelPushFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3CancelPushFrame implements Http3CancelPushFrame { 24 | private final long id; 25 | 26 | public DefaultHttp3CancelPushFrame(long id) { 27 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 28 | } 29 | 30 | @Override 31 | public long id() { 32 | return id; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) { 38 | return true; 39 | } 40 | if (o == null || getClass() != o.getClass()) { 41 | return false; 42 | } 43 | DefaultHttp3CancelPushFrame that = (DefaultHttp3CancelPushFrame) o; 44 | return id == that.id; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(id); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return StringUtil.simpleClassName(this) + "(id=" + id() + ')'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3DataFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.DefaultByteBufHolder; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | public final class DefaultHttp3DataFrame extends DefaultByteBufHolder implements Http3DataFrame { 23 | 24 | public DefaultHttp3DataFrame(ByteBuf data) { 25 | super(data); 26 | } 27 | 28 | @Override 29 | public Http3DataFrame copy() { 30 | return new DefaultHttp3DataFrame(content().copy()); 31 | } 32 | 33 | @Override 34 | public Http3DataFrame duplicate() { 35 | return new DefaultHttp3DataFrame(content().duplicate()); 36 | } 37 | 38 | @Override 39 | public Http3DataFrame retainedDuplicate() { 40 | return new DefaultHttp3DataFrame(content().retainedDuplicate()); 41 | } 42 | 43 | @Override 44 | public Http3DataFrame replace(ByteBuf content) { 45 | return new DefaultHttp3DataFrame(content); 46 | } 47 | 48 | @Override 49 | public Http3DataFrame retain() { 50 | super.retain(); 51 | return this; 52 | } 53 | 54 | @Override 55 | public Http3DataFrame retain(int increment) { 56 | super.retain(increment); 57 | return this; 58 | } 59 | 60 | @Override 61 | public Http3DataFrame touch() { 62 | super.touch(); 63 | return this; 64 | } 65 | 66 | @Override 67 | public Http3DataFrame touch(Object hint) { 68 | super.touch(hint); 69 | return this; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return StringUtil.simpleClassName(this) + "(content=" + content() + ')'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3GoAwayFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3GoAwayFrame implements Http3GoAwayFrame { 24 | private final long id; 25 | 26 | public DefaultHttp3GoAwayFrame(long id) { 27 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 28 | } 29 | 30 | @Override 31 | public long id() { 32 | return id; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) { 38 | return true; 39 | } 40 | if (o == null || getClass() != o.getClass()) { 41 | return false; 42 | } 43 | DefaultHttp3GoAwayFrame that = (DefaultHttp3GoAwayFrame) o; 44 | return id == that.id; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(id); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return StringUtil.simpleClassName(this) + "(id=" + id() + ')'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3HeadersFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3HeadersFrame implements Http3HeadersFrame { 24 | 25 | private final Http3Headers headers; 26 | 27 | public DefaultHttp3HeadersFrame() { 28 | this(new DefaultHttp3Headers()); 29 | } 30 | 31 | public DefaultHttp3HeadersFrame(Http3Headers headers) { 32 | this.headers = ObjectUtil.checkNotNull(headers, "headers"); 33 | } 34 | 35 | @Override 36 | public Http3Headers headers() { 37 | return headers; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) { 43 | return true; 44 | } 45 | if (o == null || getClass() != o.getClass()) { 46 | return false; 47 | } 48 | DefaultHttp3HeadersFrame that = (DefaultHttp3HeadersFrame) o; 49 | return Objects.equals(headers, that.headers); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(headers); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return StringUtil.simpleClassName(this) + "(headers=" + headers() + ')'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3MaxPushIdFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3MaxPushIdFrame implements Http3MaxPushIdFrame { 24 | private final long id; 25 | 26 | public DefaultHttp3MaxPushIdFrame(long id) { 27 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 28 | } 29 | 30 | @Override 31 | public long id() { 32 | return id; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) { 38 | return true; 39 | } 40 | if (o == null || getClass() != o.getClass()) { 41 | return false; 42 | } 43 | DefaultHttp3MaxPushIdFrame that = (DefaultHttp3MaxPushIdFrame) o; 44 | return id == that.id; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(id); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return StringUtil.simpleClassName(this) + "(id=" + id() + ')'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3PushPromiseFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3PushPromiseFrame implements Http3PushPromiseFrame { 24 | 25 | private final long id; 26 | private final Http3Headers headers; 27 | 28 | public DefaultHttp3PushPromiseFrame(long id) { 29 | this(id, new DefaultHttp3Headers()); 30 | } 31 | 32 | public DefaultHttp3PushPromiseFrame(long id, Http3Headers headers) { 33 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 34 | this.headers = ObjectUtil.checkNotNull(headers, "headers"); 35 | } 36 | 37 | @Override 38 | public long id() { 39 | return id; 40 | } 41 | 42 | @Override 43 | public Http3Headers headers() { 44 | return headers; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) { 50 | return true; 51 | } 52 | if (o == null || getClass() != o.getClass()) { 53 | return false; 54 | } 55 | DefaultHttp3PushPromiseFrame that = (DefaultHttp3PushPromiseFrame) o; 56 | return id == that.id && 57 | Objects.equals(headers, that.headers); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(id, headers); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return StringUtil.simpleClassName(this) + "(id=" + id() + ", headers=" + headers() + ')'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3SettingsFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.collection.LongObjectHashMap; 19 | import io.netty.util.collection.LongObjectMap; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | import java.util.Iterator; 23 | import java.util.Map; 24 | 25 | public final class DefaultHttp3SettingsFrame implements Http3SettingsFrame { 26 | 27 | private final LongObjectMap settings = new LongObjectHashMap<>(4); 28 | 29 | @Override 30 | public Long get(long key) { 31 | return settings.get(key); 32 | } 33 | 34 | @Override 35 | public Long put(long key, Long value) { 36 | if (Http3CodecUtils.isReservedHttp2Setting(key)) { 37 | throw new IllegalArgumentException("Setting is reserved for HTTP/2: " + key); 38 | } 39 | return settings.put(key, value); 40 | } 41 | 42 | @Override 43 | public Iterator> iterator() { 44 | return settings.entrySet().iterator(); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return settings.hashCode(); 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) { 55 | return true; 56 | } 57 | if (o == null || getClass() != o.getClass()) { 58 | return false; 59 | } 60 | DefaultHttp3SettingsFrame that = (DefaultHttp3SettingsFrame) o; 61 | return that.settings.equals(settings); 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return StringUtil.simpleClassName(this) + "(settings=" + settings + ')'; 67 | } 68 | 69 | /** 70 | * Creates a new {@link DefaultHttp3SettingsFrame} which is a copy of the given settings. 71 | * 72 | * @param settingsFrame the frame to copy. 73 | * @return the newly created copy. 74 | */ 75 | public static DefaultHttp3SettingsFrame copyOf(Http3SettingsFrame settingsFrame) { 76 | DefaultHttp3SettingsFrame copy = new DefaultHttp3SettingsFrame(); 77 | if (settingsFrame instanceof DefaultHttp3SettingsFrame) { 78 | copy.settings.putAll(((DefaultHttp3SettingsFrame) settingsFrame).settings); 79 | } else { 80 | for (Map.Entry entry: settingsFrame) { 81 | copy.put(entry.getKey(), entry.getValue()); 82 | } 83 | } 84 | return copy; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/DefaultHttp3UnknownFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.DefaultByteBufHolder; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | import java.util.Objects; 23 | 24 | public final class DefaultHttp3UnknownFrame extends DefaultByteBufHolder implements Http3UnknownFrame { 25 | private final long type; 26 | 27 | public DefaultHttp3UnknownFrame(long type, ByteBuf payload) { 28 | super(payload); 29 | this.type = Http3CodecUtils.checkIsReservedFrameType(type); 30 | } 31 | 32 | @Override 33 | public long type() { 34 | return type; 35 | } 36 | 37 | @Override 38 | public Http3UnknownFrame copy() { 39 | return new DefaultHttp3UnknownFrame(type, content().copy()); 40 | } 41 | 42 | @Override 43 | public Http3UnknownFrame duplicate() { 44 | return new DefaultHttp3UnknownFrame(type, content().duplicate()); 45 | } 46 | 47 | @Override 48 | public Http3UnknownFrame retainedDuplicate() { 49 | return new DefaultHttp3UnknownFrame(type, content().retainedDuplicate()); 50 | } 51 | 52 | @Override 53 | public Http3UnknownFrame replace(ByteBuf content) { 54 | return new DefaultHttp3UnknownFrame(type, content); 55 | } 56 | 57 | @Override 58 | public Http3UnknownFrame retain() { 59 | super.retain(); 60 | return this; 61 | } 62 | 63 | @Override 64 | public Http3UnknownFrame retain(int increment) { 65 | super.retain(increment); 66 | return this; 67 | } 68 | 69 | @Override 70 | public Http3UnknownFrame touch() { 71 | super.touch(); 72 | return this; 73 | } 74 | 75 | @Override 76 | public Http3UnknownFrame touch(Object hint) { 77 | super.touch(hint); 78 | return this; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return StringUtil.simpleClassName(this) + "(type=" + type() + ", content=" + content() + ')'; 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) { 89 | return true; 90 | } 91 | if (o == null || getClass() != o.getClass()) { 92 | return false; 93 | } 94 | DefaultHttp3UnknownFrame that = (DefaultHttp3UnknownFrame) o; 95 | if (type != that.type) { 96 | return false; 97 | } 98 | return super.equals(o); 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | return Objects.hash(super.hashCode(), type); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3CancelPushFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * See CANCEL_PUSH. 20 | */ 21 | public interface Http3CancelPushFrame extends Http3ControlStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the push id that identifies the server push that is being cancelled. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3ClientConnectionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | import java.util.function.LongFunction; 24 | 25 | public final class Http3ClientConnectionHandler extends Http3ConnectionHandler { 26 | 27 | private final LongFunction pushStreamHandlerFactory; 28 | 29 | /** 30 | * Create a new instance. 31 | */ 32 | public Http3ClientConnectionHandler() { 33 | this(null, null, null, null, true); 34 | } 35 | 36 | /** 37 | * Create a new instance. 38 | * 39 | * @param inboundControlStreamHandler the {@link ChannelHandler} which will be notified about 40 | * {@link Http3RequestStreamFrame}s or {@code null} if the user is not 41 | * interested in these. 42 | * @param pushStreamHandlerFactory the {@link LongFunction} that will provide a custom 43 | * {@link ChannelHandler} for push streams {@code null} if no special 44 | * handling should be done. When present, push ID will be passed as an 45 | * argument to the {@link LongFunction}. 46 | * @param unknownInboundStreamHandlerFactory the {@link LongFunction} that will provide a custom 47 | * {@link ChannelHandler} for unknown inbound stream types or 48 | * {@code null} if no special handling should be done. 49 | * @param localSettings the local {@link Http3SettingsFrame} that should be sent to the 50 | * remote peer or {@code null} if the default settings should be used. 51 | * @param disableQpackDynamicTable If QPACK dynamic table should be disabled. 52 | */ 53 | public Http3ClientConnectionHandler(@Nullable ChannelHandler inboundControlStreamHandler, 54 | @Nullable LongFunction pushStreamHandlerFactory, 55 | @Nullable LongFunction unknownInboundStreamHandlerFactory, 56 | @Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) { 57 | super(false, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings, 58 | disableQpackDynamicTable); 59 | this.pushStreamHandlerFactory = pushStreamHandlerFactory; 60 | } 61 | 62 | @Override 63 | void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel channel) { 64 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.1 65 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_STREAM_CREATION_ERROR, 66 | "Server initiated bidirectional streams are not allowed", true); 67 | } 68 | 69 | @Override 70 | void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) { 71 | final long maxTableCapacity = maxTableCapacity(); 72 | streamChannel.pipeline().addLast( 73 | new Http3UnidirectionalStreamInboundClientHandler(codecFactory, 74 | localControlStreamHandler, remoteControlStreamHandler, 75 | unknownInboundStreamHandlerFactory, pushStreamHandlerFactory, 76 | () -> new QpackEncoderHandler(maxTableCapacity, qpackDecoder), 77 | () -> new QpackDecoderHandler(qpackEncoder))); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3ControlStreamFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Marker interface for frames that can be sent and received on a 20 | * HTTP3 control stream. 21 | */ 22 | public interface Http3ControlStreamFrame extends Http3Frame { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3ControlStreamFrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Validate that the frame type is valid for a control stream. 20 | */ 21 | final class Http3ControlStreamFrameTypeValidator implements Http3FrameTypeValidator { 22 | 23 | static final Http3ControlStreamFrameTypeValidator INSTANCE = new Http3ControlStreamFrameTypeValidator(); 24 | 25 | private Http3ControlStreamFrameTypeValidator() { } 26 | 27 | @Override 28 | public void validate(long type, boolean first) throws Http3Exception { 29 | switch ((int) type) { 30 | case Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE: 31 | case Http3CodecUtils.HTTP3_HEADERS_FRAME_TYPE: 32 | case Http3CodecUtils.HTTP3_DATA_FRAME_TYPE: 33 | if (first) { 34 | throw new Http3Exception(Http3ErrorCode.H3_MISSING_SETTINGS, 35 | "Missing settings frame."); 36 | } 37 | throw new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 38 | "Unexpected frame type '" + type + "' received"); 39 | default: 40 | break; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3ControlStreamOutboundHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.channel.ChannelPromise; 22 | import io.netty.channel.socket.ChannelInputShutdownEvent; 23 | import io.netty.util.ReferenceCountUtil; 24 | import io.netty.util.internal.ObjectUtil; 25 | import org.jetbrains.annotations.Nullable; 26 | 27 | import static io.netty.incubator.codec.http3.Http3CodecUtils.closeOnFailure; 28 | 29 | final class Http3ControlStreamOutboundHandler 30 | extends Http3FrameTypeDuplexValidationHandler { 31 | private final boolean server; 32 | private final ChannelHandler codec; 33 | private Long sentMaxPushId; 34 | private Long sendGoAwayId; 35 | private Http3SettingsFrame localSettings; 36 | 37 | Http3ControlStreamOutboundHandler(boolean server, Http3SettingsFrame localSettings, ChannelHandler codec) { 38 | super(Http3ControlStreamFrame.class); 39 | this.server = server; 40 | this.localSettings = ObjectUtil.checkNotNull(localSettings, "localSettings"); 41 | this.codec = ObjectUtil.checkNotNull(codec, "codec"); 42 | } 43 | 44 | /** 45 | * Returns the last id that was sent in a MAX_PUSH_ID frame or {@code null} if none was sent yet. 46 | * 47 | * @return the id. 48 | */ 49 | @Nullable 50 | Long sentMaxPushId() { 51 | return sentMaxPushId; 52 | } 53 | 54 | @Override 55 | public void channelActive(ChannelHandlerContext ctx) { 56 | // We need to write 0x00 into the stream before doing anything else. 57 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1 58 | // Just allocate 8 bytes which would be the max needed. 59 | ByteBuf buffer = ctx.alloc().buffer(8); 60 | Http3CodecUtils.writeVariableLengthInteger(buffer, Http3CodecUtils.HTTP3_CONTROL_STREAM_TYPE); 61 | ctx.write(buffer); 62 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames. This needs to happen after 63 | // we did write the type via a ByteBuf. 64 | ctx.pipeline().addFirst(codec); 65 | 66 | assert localSettings != null; 67 | // If writing of the local settings fails let's just teardown the connection. 68 | closeOnFailure(ctx.writeAndFlush(localSettings)); 69 | 70 | // Let the GC collect localSettings. 71 | localSettings = null; 72 | 73 | ctx.fireChannelActive(); 74 | } 75 | 76 | @Override 77 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 78 | if (evt instanceof ChannelInputShutdownEvent) { 79 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1 80 | Http3CodecUtils.criticalStreamClosed(ctx); 81 | } 82 | ctx.fireUserEventTriggered(evt); 83 | } 84 | 85 | @Override 86 | public void channelInactive(ChannelHandlerContext ctx) { 87 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1 88 | Http3CodecUtils.criticalStreamClosed(ctx); 89 | ctx.fireChannelInactive(); 90 | } 91 | 92 | @Override 93 | void write(ChannelHandlerContext ctx, Http3ControlStreamFrame msg, ChannelPromise promise) { 94 | if (msg instanceof Http3MaxPushIdFrame && !handleHttp3MaxPushIdFrame(promise, (Http3MaxPushIdFrame) msg)) { 95 | ReferenceCountUtil.release(msg); 96 | return; 97 | } else if (msg instanceof Http3GoAwayFrame && !handleHttp3GoAwayFrame(promise, (Http3GoAwayFrame) msg)) { 98 | ReferenceCountUtil.release(msg); 99 | return; 100 | } 101 | 102 | ctx.write(msg, promise); 103 | } 104 | 105 | private boolean handleHttp3MaxPushIdFrame(ChannelPromise promise, Http3MaxPushIdFrame maxPushIdFrame) { 106 | long id = maxPushIdFrame.id(); 107 | 108 | // See https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-32#section-7.2.7 109 | if (sentMaxPushId != null && id < sentMaxPushId) { 110 | promise.setFailure(new Http3Exception(Http3ErrorCode.H3_ID_ERROR, "MAX_PUSH_ID reduced limit.")); 111 | return false; 112 | } 113 | 114 | sentMaxPushId = maxPushIdFrame.id(); 115 | return true; 116 | } 117 | 118 | private boolean handleHttp3GoAwayFrame(ChannelPromise promise, Http3GoAwayFrame goAwayFrame) { 119 | long id = goAwayFrame.id(); 120 | 121 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-5.2 122 | if (server && id % 4 != 0) { 123 | promise.setFailure(new Http3Exception(Http3ErrorCode.H3_ID_ERROR, 124 | "GOAWAY id not valid : " + id)); 125 | return false; 126 | } 127 | 128 | if (sendGoAwayId != null && id > sendGoAwayId) { 129 | promise.setFailure(new Http3Exception(Http3ErrorCode.H3_ID_ERROR, 130 | "GOAWAY id is bigger then the last sent: " + id + " > " + sendGoAwayId)); 131 | return false; 132 | } 133 | 134 | sendGoAwayId = id; 135 | return true; 136 | } 137 | 138 | @Override 139 | public boolean isSharable() { 140 | // This handle keeps state so we cant reuse it. 141 | return false; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3DataFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.ByteBufHolder; 20 | 21 | /** 22 | * See DATA. 23 | */ 24 | public interface Http3DataFrame extends ByteBufHolder, Http3RequestStreamFrame, Http3PushStreamFrame { 25 | 26 | @Override 27 | default long type() { 28 | return Http3CodecUtils.HTTP3_DATA_FRAME_TYPE; 29 | } 30 | 31 | @Override 32 | Http3DataFrame copy(); 33 | 34 | @Override 35 | Http3DataFrame duplicate(); 36 | 37 | @Override 38 | Http3DataFrame retainedDuplicate(); 39 | 40 | @Override 41 | Http3DataFrame replace(ByteBuf content); 42 | 43 | @Override 44 | Http3DataFrame retain(); 45 | 46 | @Override 47 | Http3DataFrame retain(int increment); 48 | 49 | @Override 50 | Http3DataFrame touch(); 51 | 52 | @Override 53 | Http3DataFrame touch(Object hint); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3ErrorCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Different HTTP3 error codes. 20 | */ 21 | public enum Http3ErrorCode { 22 | 23 | /** 24 | * No error. This is used when the connection or stream needs to be closed, but there is no error to signal. 25 | */ 26 | H3_NO_ERROR(0x100), 27 | 28 | /** 29 | * Peer violated protocol requirements in a way that does not match a more specific error code, 30 | * or endpoint declines to use the more specific error code. 31 | */ 32 | H3_GENERAL_PROTOCOL_ERROR(0x101), 33 | 34 | /** 35 | * An internal error has occurred in the HTTP stack. 36 | */ 37 | H3_INTERNAL_ERROR(0x102), 38 | 39 | /** 40 | * The endpoint detected that its peer created a stream that it will not accept. 41 | */ 42 | H3_STREAM_CREATION_ERROR(0x103), 43 | 44 | /** 45 | * A stream required by the HTTP/3 connection was closed or reset. 46 | */ 47 | H3_CLOSED_CRITICAL_STREAM(0x104), 48 | 49 | /** 50 | * A frame was received that was not permitted in the current state or on the current stream. 51 | */ 52 | H3_FRAME_UNEXPECTED(0x105), 53 | 54 | /** 55 | * A frame that fails to satisfy layout requirements or with an invalid size was received. 56 | */ 57 | H3_FRAME_ERROR(0x106), 58 | 59 | /** 60 | * The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load. 61 | */ 62 | H3_EXCESSIVE_LOAD(0x107), 63 | 64 | /** 65 | * A Stream ID or Push ID was used incorrectly, such as exceeding a limit, reducing a limit, or being reused. 66 | */ 67 | H3_ID_ERROR(0x108), 68 | 69 | /** 70 | * An endpoint detected an error in the payload of a SETTINGS frame. 71 | */ 72 | H3_SETTINGS_ERROR(0x109), 73 | 74 | /** 75 | * No SETTINGS frame was received at the beginning of the control stream. 76 | */ 77 | H3_MISSING_SETTINGS(0x10a), 78 | 79 | /** 80 | * A server rejected a request without performing any application processing. 81 | */ 82 | H3_REQUEST_REJECTED(0x10b), 83 | 84 | /** 85 | * The request or its response (including pushed response) is cancelled. 86 | */ 87 | H3_REQUEST_CANCELLED(0x10c), 88 | 89 | /** 90 | * The client's stream terminated without containing a fully-formed request. 91 | */ 92 | H3_REQUEST_INCOMPLETE(0x10d), 93 | 94 | /** 95 | * An HTTP message was malformed and cannot be processed. 96 | */ 97 | H3_MESSAGE_ERROR(0x10e), 98 | 99 | /** 100 | * The TCP connection established in response to a CONNECT request was reset or abnormally closed. 101 | */ 102 | H3_CONNECT_ERROR(0x10f), 103 | 104 | /** 105 | * The requested operation cannot be served over HTTP/3. The peer should retry over HTTP/1.1. 106 | */ 107 | H3_VERSION_FALLBACK(0x110), 108 | 109 | /** 110 | * The decoder failed to interpret an encoded field section and is not able to continue decoding that field section. 111 | */ 112 | QPACK_DECOMPRESSION_FAILED(0x200), 113 | 114 | /** 115 | * The decoder failed to interpret an encoder instruction received on the encoder stream. 116 | */ 117 | QPACK_ENCODER_STREAM_ERROR(0x201), 118 | 119 | /** 120 | * The encoder failed to interpret a decoder instruction received on the decoder stream. 121 | */ 122 | QPACK_DECODER_STREAM_ERROR(0x202); 123 | 124 | final int code; 125 | 126 | Http3ErrorCode(int code) { 127 | this.code = code; 128 | } 129 | 130 | public int code() { 131 | return code; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3Exception.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | /** 22 | * An exception related to violate the HTTP3 spec. 23 | */ 24 | public final class Http3Exception extends Exception { 25 | private final Http3ErrorCode errorCode; 26 | 27 | /** 28 | * Create a new instance. 29 | * 30 | * @param errorCode the {@link Http3ErrorCode} that caused this exception. 31 | * @param message the message to include. 32 | */ 33 | public Http3Exception(Http3ErrorCode errorCode, @Nullable String message) { 34 | super(message); 35 | this.errorCode = ObjectUtil.checkNotNull(errorCode, "errorCode"); 36 | } 37 | 38 | /** 39 | * Create a new instance. 40 | * 41 | * @param errorCode the {@link Http3ErrorCode} that caused this exception. 42 | * @param message the message to include. 43 | * @param cause the {@link Throwable} to wrap. 44 | */ 45 | public Http3Exception(Http3ErrorCode errorCode, String message, @Nullable Throwable cause) { 46 | super(message, cause); 47 | this.errorCode = ObjectUtil.checkNotNull(errorCode, "errorCode"); 48 | } 49 | 50 | /** 51 | * Returns the related {@link Http3ErrorCode}. 52 | * 53 | * @return the {@link Http3ErrorCode}. 54 | */ 55 | public Http3ErrorCode errorCode() { 56 | return errorCode; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3Frame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Marker interface that is implemented by all HTTP3 frames. 20 | */ 21 | public interface Http3Frame { 22 | /** 23 | * The type of the frame. 24 | * 25 | * @return the type. 26 | */ 27 | long type(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3FrameTypeDuplexValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelOutboundHandler; 20 | import io.netty.channel.ChannelPromise; 21 | 22 | import java.net.SocketAddress; 23 | 24 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected; 25 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.validateFrameWritten; 26 | 27 | class Http3FrameTypeDuplexValidationHandler extends Http3FrameTypeInboundValidationHandler 28 | implements ChannelOutboundHandler { 29 | 30 | Http3FrameTypeDuplexValidationHandler(Class frameType) { 31 | super(frameType); 32 | } 33 | 34 | @Override 35 | public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { 36 | T frame = validateFrameWritten(frameType, msg); 37 | if (frame != null) { 38 | write(ctx, frame, promise); 39 | } else { 40 | writeFrameDiscarded(msg, promise); 41 | } 42 | } 43 | 44 | void write(ChannelHandlerContext ctx, T msg, ChannelPromise promise) { 45 | ctx.write(msg, promise); 46 | } 47 | 48 | void writeFrameDiscarded(Object discardedFrame, ChannelPromise promise) { 49 | frameTypeUnexpected(promise, discardedFrame); 50 | } 51 | 52 | @Override 53 | public void flush(ChannelHandlerContext ctx) { 54 | ctx.flush(); 55 | } 56 | 57 | @Override 58 | public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) { 59 | ctx.bind(localAddress, promise); 60 | } 61 | 62 | @Override 63 | public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, 64 | ChannelPromise promise) throws Exception { 65 | ctx.connect(remoteAddress, localAddress, promise); 66 | } 67 | 68 | @Override 69 | public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) { 70 | ctx.disconnect(promise); 71 | } 72 | 73 | @Override 74 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 75 | ctx.close(promise); 76 | } 77 | 78 | @Override 79 | public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 80 | ctx.deregister(promise); 81 | } 82 | 83 | @Override 84 | public void read(ChannelHandlerContext ctx) throws Exception { 85 | ctx.read(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3FrameTypeInboundValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.util.internal.ObjectUtil; 21 | 22 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected; 23 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.validateFrameRead; 24 | 25 | class Http3FrameTypeInboundValidationHandler extends ChannelInboundHandlerAdapter { 26 | 27 | protected final Class frameType; 28 | 29 | Http3FrameTypeInboundValidationHandler(Class frameType) { 30 | this.frameType = ObjectUtil.checkNotNull(frameType, "frameType"); 31 | } 32 | 33 | @Override 34 | public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | final T frame = validateFrameRead(frameType, msg); 36 | if (frame != null) { 37 | channelRead(ctx, frame); 38 | } else { 39 | readFrameDiscarded(ctx, msg); 40 | } 41 | } 42 | 43 | void channelRead(ChannelHandlerContext ctx, T frame) throws Exception { 44 | ctx.fireChannelRead(frame); 45 | } 46 | 47 | void readFrameDiscarded(ChannelHandlerContext ctx, Object discardedFrame) { 48 | frameTypeUnexpected(ctx, discardedFrame); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3FrameTypeOutboundValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelOutboundHandlerAdapter; 20 | import io.netty.channel.ChannelPromise; 21 | import io.netty.util.internal.ObjectUtil; 22 | 23 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected; 24 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.validateFrameWritten; 25 | 26 | class Http3FrameTypeOutboundValidationHandler extends ChannelOutboundHandlerAdapter { 27 | 28 | private final Class frameType; 29 | 30 | Http3FrameTypeOutboundValidationHandler(Class frameType) { 31 | this.frameType = ObjectUtil.checkNotNull(frameType, "frameType"); 32 | } 33 | 34 | @Override 35 | public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { 36 | T frame = validateFrameWritten(frameType, msg); 37 | if (frame != null) { 38 | write(ctx, frame, promise); 39 | } else { 40 | writeFrameDiscarded(msg, promise); 41 | } 42 | } 43 | 44 | void write(ChannelHandlerContext ctx, T msg, ChannelPromise promise) { 45 | ctx.write(msg, promise); 46 | } 47 | 48 | void writeFrameDiscarded(Object discardedFrame, ChannelPromise promise) { 49 | frameTypeUnexpected(promise, discardedFrame); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3FrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | @FunctionalInterface 19 | interface Http3FrameTypeValidator { 20 | 21 | Http3FrameTypeValidator NO_VALIDATION = (type, first) -> { }; 22 | 23 | void validate(long type, boolean first) throws Http3Exception; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3FrameValidationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelPromise; 20 | import io.netty.util.ReferenceCountUtil; 21 | import io.netty.util.internal.StringUtil; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | final class Http3FrameValidationUtils { 25 | 26 | private Http3FrameValidationUtils() { 27 | // no instances 28 | } 29 | 30 | @SuppressWarnings("unchecked") 31 | private static T cast(Object msg) { 32 | return (T) msg; 33 | } 34 | 35 | private static boolean isValid(Class frameType, Object msg) { 36 | return frameType.isInstance(msg); 37 | } 38 | 39 | /** 40 | * Check if the passed {@code msg} is of the {@code expectedFrameType} and return the expected type, else return 41 | * {@code null}. 42 | * 43 | * @param expectedFrameType {@link Class} of the expected frame type. 44 | * @param msg to validate. 45 | * @param Expected type. 46 | * @return {@code msg} as expected frame type or {@code null} if it can not be converted to the expected type. 47 | */ 48 | @Nullable 49 | static T validateFrameWritten(Class expectedFrameType, Object msg) { 50 | if (isValid(expectedFrameType, msg)) { 51 | return cast(msg); 52 | } 53 | return null; 54 | } 55 | 56 | /** 57 | * Check if the passed {@code msg} is of the {@code expectedFrameType} and return the expected type, else return 58 | * {@code null}. 59 | * 60 | * @param expectedFrameType {@link Class} of the expected frame type. 61 | * @param msg to validate. 62 | * @param Expected type. 63 | * @return {@code msg} as expected frame type or {@code null} if it can not be converted to the expected type. 64 | */ 65 | @Nullable 66 | static T validateFrameRead(Class expectedFrameType, Object msg) { 67 | if (isValid(expectedFrameType, msg)) { 68 | return cast(msg); 69 | } 70 | return null; 71 | } 72 | 73 | /** 74 | * Handle unexpected frame type by failing the passed {@link ChannelPromise}. 75 | * 76 | * @param promise to fail. 77 | * @param frame which is unexpected. 78 | */ 79 | static void frameTypeUnexpected(ChannelPromise promise, Object frame) { 80 | String type = StringUtil.simpleClassName(frame); 81 | ReferenceCountUtil.release(frame); 82 | promise.setFailure(new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 83 | "Frame of type " + type + " unexpected")); 84 | } 85 | 86 | /** 87 | * Handle unexpected frame type by propagating a connection error with code: 88 | * {@link Http3ErrorCode#H3_FRAME_UNEXPECTED}. 89 | * 90 | * @param ctx to use for propagation of failure. 91 | * @param frame which is unexpected. 92 | */ 93 | static void frameTypeUnexpected(ChannelHandlerContext ctx, Object frame) { 94 | ReferenceCountUtil.release(frame); 95 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_FRAME_UNEXPECTED, "Frame type unexpected", true); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3GoAwayFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * See GOAWAY. 20 | */ 21 | public interface Http3GoAwayFrame extends Http3ControlStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the id. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3HeadersFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * See HEADERS. 20 | */ 21 | public interface Http3HeadersFrame extends Http3RequestStreamFrame, Http3PushStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_HEADERS_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the carried headers. 30 | * 31 | * @return the carried headers. 32 | */ 33 | Http3Headers headers(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3HeadersValidationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Thrown if {@link Http3Headers} validation fails for some reason. 20 | */ 21 | public final class Http3HeadersValidationException extends RuntimeException { 22 | 23 | /** 24 | * Create a new instance. 25 | * 26 | * @param message the message. 27 | */ 28 | public Http3HeadersValidationException(String message) { 29 | super(message); 30 | } 31 | 32 | /** 33 | * Create a new instance. 34 | * 35 | * @param message the message. 36 | * @param cause the wrapped {@link Throwable}. 37 | */ 38 | public Http3HeadersValidationException(String message, Throwable cause) { 39 | super(message, cause); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3MaxPushIdFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * See MAX_PUSH_ID. 20 | */ 21 | public interface Http3MaxPushIdFrame extends Http3ControlStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the maximum value for a Push ID that the server can use. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushPromiseFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * See PUSH_PROMISE. 20 | */ 21 | public interface Http3PushPromiseFrame extends Http3RequestStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the push id. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | 35 | /** 36 | * Returns the carried headers. 37 | * 38 | * @return the headers. 39 | */ 40 | Http3Headers headers(); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushStreamClientInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelInitializer; 19 | import io.netty.channel.ChannelPipeline; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | 22 | import static io.netty.incubator.codec.http3.Http3CodecUtils.isServerInitiatedQuicStream; 23 | import static io.netty.incubator.codec.http3.Http3RequestStreamCodecState.NO_STATE; 24 | 25 | /** 26 | * Abstract base class that users can extend to init HTTP/3 push-streams for clients. This initializer 27 | * will automatically add HTTP/3 codecs etc to the {@link ChannelPipeline} as well. 28 | */ 29 | public abstract class Http3PushStreamClientInitializer extends ChannelInitializer { 30 | 31 | @Override 32 | protected final void initChannel(QuicStreamChannel ch) { 33 | if (isServerInitiatedQuicStream(ch)) { 34 | throw new IllegalArgumentException("Using client push stream initializer for server stream: " + 35 | ch.streamId()); 36 | } 37 | Http3CodecUtils.verifyIsUnidirectional(ch); 38 | 39 | Http3ConnectionHandler connectionHandler = Http3CodecUtils.getConnectionHandlerOrClose(ch.parent()); 40 | if (connectionHandler == null) { 41 | // connection should have been closed 42 | return; 43 | } 44 | ChannelPipeline pipeline = ch.pipeline(); 45 | Http3RequestStreamDecodeStateValidator decodeStateValidator = new Http3RequestStreamDecodeStateValidator(); 46 | // Add the encoder and decoder in the pipeline, so we can handle Http3Frames 47 | pipeline.addLast(connectionHandler.newCodec(NO_STATE, decodeStateValidator)); 48 | pipeline.addLast(decodeStateValidator); 49 | // Add the handler that will validate what we write and receive on this stream. 50 | pipeline.addLast(connectionHandler.newPushStreamValidationHandler(ch, decodeStateValidator)); 51 | initPushStream(ch); 52 | } 53 | 54 | /** 55 | * Initialize the {@link QuicStreamChannel} to handle {@link Http3PushStreamFrame}s. At the point of calling this 56 | * method it is already valid to write {@link Http3PushStreamFrame}s as the codec is already in the pipeline. 57 | * 58 | * @param ch the {QuicStreamChannel} for the push stream. 59 | */ 60 | protected abstract void initPushStream(QuicStreamChannel ch); 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushStreamClientValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.socket.ChannelInputShutdownReadComplete; 20 | 21 | import static io.netty.incubator.codec.http3.Http3RequestStreamValidationUtils.INVALID_FRAME_READ; 22 | import static io.netty.incubator.codec.http3.Http3RequestStreamValidationUtils.sendStreamAbandonedIfRequired; 23 | import static io.netty.incubator.codec.http3.Http3RequestStreamValidationUtils.validateDataFrameRead; 24 | import static io.netty.incubator.codec.http3.Http3RequestStreamValidationUtils.validateHeaderFrameRead; 25 | import static io.netty.incubator.codec.http3.Http3RequestStreamValidationUtils.validateOnStreamClosure; 26 | 27 | final class Http3PushStreamClientValidationHandler 28 | extends Http3FrameTypeInboundValidationHandler { 29 | private final QpackAttributes qpackAttributes; 30 | private final QpackDecoder qpackDecoder; 31 | private final Http3RequestStreamCodecState decodeState; 32 | 33 | private long expectedLength = -1; 34 | private long seenLength; 35 | 36 | Http3PushStreamClientValidationHandler(QpackAttributes qpackAttributes, QpackDecoder qpackDecoder, 37 | Http3RequestStreamCodecState decodeState) { 38 | super(Http3RequestStreamFrame.class); 39 | this.qpackAttributes = qpackAttributes; 40 | this.qpackDecoder = qpackDecoder; 41 | this.decodeState = decodeState; 42 | } 43 | 44 | @Override 45 | void channelRead(ChannelHandlerContext ctx, Http3RequestStreamFrame frame) { 46 | if (frame instanceof Http3PushPromiseFrame) { 47 | ctx.fireChannelRead(frame); 48 | return; 49 | } 50 | 51 | if (frame instanceof Http3HeadersFrame) { 52 | Http3HeadersFrame headersFrame = (Http3HeadersFrame) frame; 53 | long maybeContentLength = validateHeaderFrameRead(headersFrame, ctx, decodeState); 54 | if (maybeContentLength >= 0) { 55 | expectedLength = maybeContentLength; 56 | } else if (maybeContentLength == INVALID_FRAME_READ) { 57 | return; 58 | } 59 | } 60 | 61 | if (frame instanceof Http3DataFrame) { 62 | final Http3DataFrame dataFrame = (Http3DataFrame) frame; 63 | long maybeContentLength = validateDataFrameRead(dataFrame, ctx, expectedLength, seenLength, false); 64 | if (maybeContentLength >= 0) { 65 | seenLength = maybeContentLength; 66 | } else if (maybeContentLength == INVALID_FRAME_READ) { 67 | return; 68 | } 69 | } 70 | ctx.fireChannelRead(frame); 71 | } 72 | 73 | @Override 74 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 75 | if (evt == ChannelInputShutdownReadComplete.INSTANCE) { 76 | sendStreamAbandonedIfRequired(ctx, qpackAttributes, qpackDecoder, decodeState); 77 | if (!validateOnStreamClosure(ctx, expectedLength, seenLength, false)) { 78 | return; 79 | } 80 | } 81 | ctx.fireUserEventTriggered(evt); 82 | } 83 | 84 | @Override 85 | public boolean isSharable() { 86 | // This handle keeps state so we can't share it. 87 | return false; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushStreamFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Marker interface for frames that can be sent and received on a 20 | * HTTP3 push stream. 21 | */ 22 | public interface Http3PushStreamFrame extends Http3Frame { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushStreamFrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Validate that the frame type is valid for a push stream. 20 | */ 21 | final class Http3PushStreamFrameTypeValidator implements Http3FrameTypeValidator { 22 | 23 | static final Http3PushStreamFrameTypeValidator INSTANCE = new Http3PushStreamFrameTypeValidator(); 24 | 25 | private Http3PushStreamFrameTypeValidator() { } 26 | 27 | @Override 28 | public void validate(long type, boolean first) throws Http3Exception { 29 | switch ((int) type) { 30 | case Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE: 31 | case Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE: 32 | case Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE: 33 | case Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE: 34 | case Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE: 35 | throw new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 36 | "Unexpected frame type '" + type + "' received"); 37 | default: 38 | break; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushStreamServerInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.ChannelInitializer; 20 | import io.netty.channel.ChannelPipeline; 21 | import io.netty.incubator.codec.quic.QuicStreamChannel; 22 | 23 | import static io.netty.incubator.codec.http3.Http3CodecUtils.isServerInitiatedQuicStream; 24 | import static io.netty.incubator.codec.http3.Http3CodecUtils.writeVariableLengthInteger; 25 | import static io.netty.incubator.codec.http3.Http3RequestStreamCodecState.NO_STATE; 26 | import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; 27 | 28 | /** 29 | * Abstract base class that users can extend to init HTTP/3 push-streams for servers. This initializer 30 | * will automatically add HTTP/3 codecs etc to the {@link ChannelPipeline} as well. 31 | */ 32 | public abstract class Http3PushStreamServerInitializer extends ChannelInitializer { 33 | 34 | private final long pushId; 35 | 36 | protected Http3PushStreamServerInitializer(long pushId) { 37 | this.pushId = checkPositiveOrZero(pushId, "pushId"); 38 | } 39 | 40 | @Override 41 | protected final void initChannel(QuicStreamChannel ch) { 42 | if (!isServerInitiatedQuicStream(ch)) { 43 | throw new IllegalArgumentException("Using server push stream initializer for client stream: " + 44 | ch.streamId()); 45 | } 46 | Http3CodecUtils.verifyIsUnidirectional(ch); 47 | 48 | // We need to write stream type into the stream before doing anything else. 49 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1 50 | // Just allocate 16 bytes which would be the max needed to write 2 variable length ints. 51 | ByteBuf buffer = ch.alloc().buffer(16); 52 | writeVariableLengthInteger(buffer, Http3CodecUtils.HTTP3_PUSH_STREAM_TYPE); 53 | writeVariableLengthInteger(buffer, pushId); 54 | ch.write(buffer); 55 | 56 | Http3ConnectionHandler connectionHandler = Http3CodecUtils.getConnectionHandlerOrClose(ch.parent()); 57 | if (connectionHandler == null) { 58 | // connection should have been closed 59 | return; 60 | } 61 | 62 | ChannelPipeline pipeline = ch.pipeline(); 63 | Http3RequestStreamEncodeStateValidator encodeStateValidator = new Http3RequestStreamEncodeStateValidator(); 64 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames 65 | pipeline.addLast(connectionHandler.newCodec(encodeStateValidator, NO_STATE)); 66 | pipeline.addLast(encodeStateValidator); 67 | // Add the handler that will validate what we write and receive on this stream. 68 | pipeline.addLast(connectionHandler.newPushStreamValidationHandler(ch, NO_STATE)); 69 | initPushStream(ch); 70 | } 71 | 72 | /** 73 | * Initialize the {@link QuicStreamChannel} to handle {@link Http3PushStreamFrame}s. At the point of calling this 74 | * method it is already valid to write {@link Http3PushStreamFrame}s as the codec is already in the pipeline. 75 | * 76 | * @param ch the {QuicStreamChannel} for the push stream. 77 | */ 78 | protected abstract void initPushStream(QuicStreamChannel ch); 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3PushStreamServerValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * See HTTP Message Exchanges. 20 | */ 21 | final class Http3PushStreamServerValidationHandler 22 | extends Http3FrameTypeOutboundValidationHandler { 23 | 24 | static final Http3PushStreamServerValidationHandler INSTANCE = new Http3PushStreamServerValidationHandler(); 25 | 26 | private Http3PushStreamServerValidationHandler() { 27 | super(Http3PushStreamFrame.class); 28 | } 29 | 30 | @Override 31 | public boolean isSharable() { 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3RequestStreamCodecState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * State of encoding or decoding for a stream following the 21 | * HTTP message exchange semantics 22 | */ 23 | interface Http3RequestStreamCodecState { 24 | /** 25 | * An implementation of {@link Http3RequestStreamCodecState} that managed no state. 26 | */ 27 | Http3RequestStreamCodecState NO_STATE = new Http3RequestStreamCodecState() { 28 | @Override 29 | public boolean started() { 30 | return false; 31 | } 32 | 33 | @Override 34 | public boolean receivedFinalHeaders() { 35 | return false; 36 | } 37 | 38 | @Override 39 | public boolean terminated() { 40 | return false; 41 | } 42 | }; 43 | 44 | /** 45 | * If any {@link Http3HeadersFrame} or {@link Http3DataFrame} has been received/sent on this stream. 46 | * 47 | * @return {@code true} if any {@link Http3HeadersFrame} or {@link Http3DataFrame} has been received/sent on this 48 | * stream. 49 | */ 50 | boolean started(); 51 | 52 | /** 53 | * If a final {@link Http3HeadersFrame} has been received/sent before {@link Http3DataFrame} starts. 54 | * 55 | * @return {@code true} if a final {@link Http3HeadersFrame} has been received/sent before {@link Http3DataFrame} 56 | * starts 57 | */ 58 | boolean receivedFinalHeaders(); 59 | 60 | /** 61 | * If no more frames are expected on this stream. 62 | * 63 | * @return {@code true} if no more frames are expected on this stream. 64 | */ 65 | boolean terminated(); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3RequestStreamDecodeStateValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.incubator.codec.http3.Http3RequestStreamEncodeStateValidator.State; 21 | 22 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected; 23 | import static io.netty.incubator.codec.http3.Http3RequestStreamEncodeStateValidator.evaluateFrame; 24 | import static io.netty.incubator.codec.http3.Http3RequestStreamEncodeStateValidator.isFinalHeadersReceived; 25 | import static io.netty.incubator.codec.http3.Http3RequestStreamEncodeStateValidator.isStreamStarted; 26 | import static io.netty.incubator.codec.http3.Http3RequestStreamEncodeStateValidator.isTrailersReceived; 27 | 28 | final class Http3RequestStreamDecodeStateValidator extends ChannelInboundHandlerAdapter 29 | implements Http3RequestStreamCodecState { 30 | private State state = State.None; 31 | 32 | @Override 33 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 34 | if (!(msg instanceof Http3RequestStreamFrame)) { 35 | super.channelRead(ctx, msg); 36 | return; 37 | } 38 | final Http3RequestStreamFrame frame = (Http3RequestStreamFrame) msg; 39 | final State nextState = evaluateFrame(state, frame); 40 | if (nextState == null) { 41 | frameTypeUnexpected(ctx, msg); 42 | return; 43 | } 44 | state = nextState; 45 | super.channelRead(ctx, msg); 46 | } 47 | 48 | @Override 49 | public boolean started() { 50 | return isStreamStarted(state); 51 | } 52 | 53 | @Override 54 | public boolean receivedFinalHeaders() { 55 | return isFinalHeadersReceived(state); 56 | } 57 | 58 | @Override 59 | public boolean terminated() { 60 | return isTrailersReceived(state); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3RequestStreamEncodeStateValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelOutboundHandlerAdapter; 20 | import io.netty.channel.ChannelPromise; 21 | import io.netty.handler.codec.http.HttpStatusClass; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import static io.netty.incubator.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected; 25 | 26 | final class Http3RequestStreamEncodeStateValidator extends ChannelOutboundHandlerAdapter 27 | implements Http3RequestStreamCodecState { 28 | enum State { 29 | None, 30 | Headers, 31 | FinalHeaders, 32 | Trailers 33 | } 34 | private State state = State.None; 35 | 36 | @Override 37 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 38 | if (!(msg instanceof Http3RequestStreamFrame)) { 39 | super.write(ctx, msg, promise); 40 | return; 41 | } 42 | final Http3RequestStreamFrame frame = (Http3RequestStreamFrame) msg; 43 | final State nextState = evaluateFrame(state, frame); 44 | if (nextState == null) { 45 | frameTypeUnexpected(ctx, msg); 46 | return; 47 | } 48 | state = nextState; 49 | super.write(ctx, msg, promise); 50 | } 51 | 52 | @Override 53 | public boolean started() { 54 | return isStreamStarted(state); 55 | } 56 | 57 | @Override 58 | public boolean receivedFinalHeaders() { 59 | return isFinalHeadersReceived(state); 60 | } 61 | 62 | @Override 63 | public boolean terminated() { 64 | return isTrailersReceived(state); 65 | } 66 | 67 | /** 68 | * Evaluates the passed frame and returns the following: 69 | *
    70 | *
  • Modified {@link State} if the state should be changed.
  • 71 | *
  • Same {@link State} as the passed {@code state} if no state change is necessary
  • 72 | *
  • {@code null} if the frame is unexpected
  • 73 | *
74 | * 75 | * @param state Current state. 76 | * @param frame to evaluate. 77 | * @return Next {@link State} or {@code null} if the frame is invalid. 78 | */ 79 | @Nullable 80 | static State evaluateFrame(State state, Http3RequestStreamFrame frame) { 81 | if (frame instanceof Http3PushPromiseFrame || frame instanceof Http3UnknownFrame) { 82 | // always allow push promise frames. 83 | return state; 84 | } 85 | switch (state) { 86 | case None: 87 | case Headers: 88 | if (!(frame instanceof Http3HeadersFrame)) { 89 | return null; 90 | } 91 | return isInformationalResponse((Http3HeadersFrame) frame) ? State.Headers : State.FinalHeaders; 92 | case FinalHeaders: 93 | if (frame instanceof Http3HeadersFrame) { 94 | if (isInformationalResponse((Http3HeadersFrame) frame)) { 95 | // Information response after final response headers 96 | return null; 97 | } 98 | // trailers 99 | return State.Trailers; 100 | } 101 | return state; 102 | case Trailers: 103 | return null; 104 | default: 105 | throw new Error(); 106 | } 107 | } 108 | 109 | static boolean isStreamStarted(State state) { 110 | return state != State.None; 111 | } 112 | 113 | static boolean isFinalHeadersReceived(State state) { 114 | return isStreamStarted(state) && state != State.Headers; 115 | } 116 | 117 | static boolean isTrailersReceived(State state) { 118 | return state == State.Trailers; 119 | } 120 | 121 | private static boolean isInformationalResponse(Http3HeadersFrame headersFrame) { 122 | return HttpStatusClass.valueOf(headersFrame.headers().status()) == HttpStatusClass.INFORMATIONAL; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3RequestStreamFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Marker interface for frames that can be sent and received on a 20 | * HTTP3 request stream. 21 | */ 22 | public interface Http3RequestStreamFrame extends Http3Frame { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3RequestStreamFrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | /** 19 | * Validate that the frame type is valid for a request stream. 20 | */ 21 | final class Http3RequestStreamFrameTypeValidator implements Http3FrameTypeValidator { 22 | 23 | static final Http3RequestStreamFrameTypeValidator INSTANCE = new Http3RequestStreamFrameTypeValidator(); 24 | 25 | private Http3RequestStreamFrameTypeValidator() { } 26 | 27 | @Override 28 | public void validate(long type, boolean first) throws Http3Exception { 29 | switch ((int) type) { 30 | case Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE: 31 | case Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE: 32 | case Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE: 33 | case Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE: 34 | throw new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 35 | "Unexpected frame type '" + type + "' received"); 36 | default: 37 | break; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3RequestStreamInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelInitializer; 19 | import io.netty.channel.ChannelPipeline; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | import io.netty.util.internal.StringUtil; 22 | 23 | /** 24 | * Abstract base class that users can extend to init HTTP/3 request-streams. This initializer 25 | * will automatically add HTTP/3 codecs etc to the {@link ChannelPipeline} as well. 26 | */ 27 | public abstract class Http3RequestStreamInitializer extends ChannelInitializer { 28 | 29 | @Override 30 | protected final void initChannel(QuicStreamChannel ch) { 31 | ChannelPipeline pipeline = ch.pipeline(); 32 | Http3ConnectionHandler connectionHandler = ch.parent().pipeline().get(Http3ConnectionHandler.class); 33 | if (connectionHandler == null) { 34 | throw new IllegalStateException("Couldn't obtain the " + 35 | StringUtil.simpleClassName(Http3ConnectionHandler.class) + " of the parent Channel"); 36 | } 37 | 38 | Http3RequestStreamEncodeStateValidator encodeStateValidator = new Http3RequestStreamEncodeStateValidator(); 39 | Http3RequestStreamDecodeStateValidator decodeStateValidator = new Http3RequestStreamDecodeStateValidator(); 40 | 41 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames 42 | pipeline.addLast(connectionHandler.newCodec(encodeStateValidator, decodeStateValidator)); 43 | // Add the handler that will validate what we write and receive on this stream. 44 | pipeline.addLast(encodeStateValidator); 45 | pipeline.addLast(decodeStateValidator); 46 | pipeline.addLast(connectionHandler.newRequestStreamValidationHandler(ch, encodeStateValidator, 47 | decodeStateValidator)); 48 | initRequestStream(ch); 49 | } 50 | 51 | /** 52 | * Init the {@link QuicStreamChannel} to handle {@link Http3RequestStreamFrame}s. At the point of calling this 53 | * method it is already valid to write {@link Http3RequestStreamFrame}s as the codec is already in the pipeline. 54 | * @param ch the {QuicStreamChannel} for the request stream. 55 | */ 56 | protected abstract void initRequestStream(QuicStreamChannel ch); 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3ServerConnectionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.ChannelPipeline; 21 | import io.netty.incubator.codec.quic.QuicStreamChannel; 22 | import io.netty.util.internal.ObjectUtil; 23 | import org.jetbrains.annotations.Nullable; 24 | 25 | import java.util.function.LongFunction; 26 | 27 | import static io.netty.incubator.codec.http3.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY; 28 | 29 | 30 | /** 31 | * Handler that handles HTTP3 for the server-side. 32 | */ 33 | public final class Http3ServerConnectionHandler extends Http3ConnectionHandler { 34 | private final ChannelHandler requestStreamHandler; 35 | 36 | /** 37 | * Create a new instance. 38 | * 39 | * @param requestStreamHandler the {@link ChannelHandler} that is used for each new request stream. 40 | * This handler will receive {@link Http3HeadersFrame} and {@link Http3DataFrame}s. 41 | */ 42 | public Http3ServerConnectionHandler(ChannelHandler requestStreamHandler) { 43 | this(requestStreamHandler, null, null, null, true); 44 | } 45 | 46 | /** 47 | * Create a new instance. 48 | * @param requestStreamHandler the {@link ChannelHandler} that is used for each new request stream. 49 | * This handler will receive {@link Http3HeadersFrame} and 50 | * {@link Http3DataFrame}s. 51 | * @param inboundControlStreamHandler the {@link ChannelHandler} which will be notified about 52 | * {@link Http3RequestStreamFrame}s or {@code null} if the user is not 53 | * interested in these. 54 | * @param unknownInboundStreamHandlerFactory the {@link LongFunction} that will provide a custom 55 | * {@link ChannelHandler} for unknown inbound stream types or 56 | * {@code null} if no special handling should be done. 57 | * @param localSettings the local {@link Http3SettingsFrame} that should be sent to the 58 | * remote peer or {@code null} if the default settings should be used. 59 | * @param disableQpackDynamicTable If QPACK dynamic table should be disabled. 60 | */ 61 | public Http3ServerConnectionHandler(ChannelHandler requestStreamHandler, 62 | @Nullable ChannelHandler inboundControlStreamHandler, 63 | @Nullable LongFunction unknownInboundStreamHandlerFactory, 64 | @Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) { 65 | super(true, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings, 66 | disableQpackDynamicTable); 67 | this.requestStreamHandler = ObjectUtil.checkNotNull(requestStreamHandler, "requestStreamHandler"); 68 | } 69 | 70 | @Override 71 | void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) { 72 | ChannelPipeline pipeline = streamChannel.pipeline(); 73 | Http3RequestStreamEncodeStateValidator encodeStateValidator = new Http3RequestStreamEncodeStateValidator(); 74 | Http3RequestStreamDecodeStateValidator decodeStateValidator = new Http3RequestStreamDecodeStateValidator(); 75 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames 76 | pipeline.addLast(newCodec(encodeStateValidator, decodeStateValidator)); 77 | pipeline.addLast(encodeStateValidator); 78 | pipeline.addLast(decodeStateValidator); 79 | pipeline.addLast(newRequestStreamValidationHandler(streamChannel, encodeStateValidator, decodeStateValidator)); 80 | pipeline.addLast(requestStreamHandler); 81 | } 82 | 83 | @Override 84 | void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) { 85 | final long maxTableCapacity = maxTableCapacity(); 86 | streamChannel.pipeline().addLast( 87 | new Http3UnidirectionalStreamInboundServerHandler(codecFactory, 88 | localControlStreamHandler, remoteControlStreamHandler, 89 | unknownInboundStreamHandlerFactory, 90 | () -> new QpackEncoderHandler(maxTableCapacity, qpackDecoder), 91 | () -> new QpackDecoderHandler(qpackEncoder))); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3SettingsFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.Map; 21 | 22 | /** 23 | * See SETTINGS. 24 | */ 25 | public interface Http3SettingsFrame extends Http3ControlStreamFrame, Iterable> { 26 | 27 | /** 28 | * See 29 | * SETTINGS_QPACK_MAX_TABLE_CAPACITY. 30 | */ 31 | long HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY = 0x1; 32 | /** 33 | * See 34 | * SETTINGS_QPACK_BLOCKED_STREAMS. 35 | */ 36 | long HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS = 0x7; 37 | /** 38 | * See 39 | * SETTINGS_MAX_FIELD_SECTION_SIZE. 40 | */ 41 | long HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE = 0x6; 42 | 43 | @Override 44 | default long type() { 45 | return Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE; 46 | } 47 | 48 | /** 49 | * Get a setting from the frame. 50 | * 51 | * @param key the key of the setting. 52 | * @return the value of the setting or {@code null} if none was found with the given key. 53 | */ 54 | @Nullable 55 | Long get(long key); 56 | 57 | /** 58 | * Get a setting from the frame. 59 | * 60 | * @param key the key of the setting. 61 | * @param defaultValue If the setting does not exist. 62 | * @return the value of the setting or {@code defaultValue} if none was found with the given key. 63 | */ 64 | default Long getOrDefault(long key, long defaultValue) { 65 | final Long val = get(key); 66 | return val == null ? defaultValue : val; 67 | } 68 | 69 | /** 70 | * Put a setting in the frame. 71 | * 72 | * @param key the key of the setting 73 | * @param value the value of the setting. 74 | * @return the previous stored valued for the given key or {@code null} if none was stored before. 75 | */ 76 | @Nullable 77 | Long put(long key, Long value); 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3UnidirectionalStreamInboundClientHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.incubator.codec.http3; 18 | 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.incubator.codec.http3.Http3FrameCodec.Http3FrameCodecFactory; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import java.util.function.LongFunction; 25 | import java.util.function.Supplier; 26 | 27 | final class Http3UnidirectionalStreamInboundClientHandler extends Http3UnidirectionalStreamInboundHandler { 28 | private final LongFunction pushStreamHandlerFactory; 29 | 30 | Http3UnidirectionalStreamInboundClientHandler( 31 | Http3FrameCodecFactory codecFactory, 32 | Http3ControlStreamInboundHandler localControlStreamHandler, 33 | Http3ControlStreamOutboundHandler remoteControlStreamHandler, 34 | @Nullable LongFunction unknownStreamHandlerFactory, 35 | @Nullable LongFunction pushStreamHandlerFactory, 36 | Supplier qpackEncoderHandlerFactory, Supplier qpackDecoderHandlerFactory) { 37 | super(codecFactory, localControlStreamHandler, remoteControlStreamHandler, unknownStreamHandlerFactory, 38 | qpackEncoderHandlerFactory, qpackDecoderHandlerFactory); 39 | this.pushStreamHandlerFactory = pushStreamHandlerFactory == null ? __ -> ReleaseHandler.INSTANCE : 40 | pushStreamHandlerFactory; 41 | } 42 | 43 | @Override 44 | void initPushStream(ChannelHandlerContext ctx, long pushId) { 45 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-4.4 46 | Long maxPushId = remoteControlStreamHandler.sentMaxPushId(); 47 | if (maxPushId == null) { 48 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, 49 | "Received push stream before sending MAX_PUSH_ID frame.", false); 50 | } else if (maxPushId < pushId) { 51 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, 52 | "Received push stream with ID " + pushId + " greater than the max push ID " + maxPushId 53 | + '.', false); 54 | } else { 55 | // Replace this handler with the actual push stream handlers. 56 | final ChannelHandler pushStreamHandler = pushStreamHandlerFactory.apply(pushId); 57 | ctx.pipeline().replace(this, null, pushStreamHandler); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3UnidirectionalStreamInboundServerHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.incubator.codec.http3; 18 | 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.incubator.codec.http3.Http3FrameCodec.Http3FrameCodecFactory; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import java.util.function.LongFunction; 25 | import java.util.function.Supplier; 26 | 27 | final class Http3UnidirectionalStreamInboundServerHandler extends Http3UnidirectionalStreamInboundHandler { 28 | 29 | Http3UnidirectionalStreamInboundServerHandler(Http3FrameCodecFactory codecFactory, 30 | Http3ControlStreamInboundHandler localControlStreamHandler, 31 | Http3ControlStreamOutboundHandler remoteControlStreamHandler, 32 | @Nullable LongFunction unknownStreamHandlerFactory, 33 | Supplier qpackEncoderHandlerFactory, 34 | Supplier qpackDecoderHandlerFactory) { 35 | super(codecFactory, localControlStreamHandler, remoteControlStreamHandler, unknownStreamHandlerFactory, 36 | qpackEncoderHandlerFactory, qpackDecoderHandlerFactory); 37 | } 38 | 39 | @Override 40 | void initPushStream(ChannelHandlerContext ctx, long id) { 41 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_STREAM_CREATION_ERROR, 42 | "Server received push stream.", false); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/Http3UnknownFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.ByteBufHolder; 20 | 21 | /** 22 | * Unknown HTTP3 frame. 23 | * These frames are valid on all stream types. 24 | *
25 |  *    HTTP/3 Frame Format {
26 |  *      Type (i),
27 |  *      Length (i),
28 |  *      Frame Payload (..),
29 |  *    }
30 |  * 
31 | */ 32 | public interface Http3UnknownFrame extends 33 | Http3RequestStreamFrame, Http3PushStreamFrame, Http3ControlStreamFrame, ByteBufHolder { 34 | 35 | /** 36 | * Return the payload length of the frame. 37 | * 38 | * @return the length. 39 | */ 40 | default long length() { 41 | return content().readableBytes(); 42 | } 43 | 44 | @Override 45 | Http3UnknownFrame copy(); 46 | 47 | @Override 48 | Http3UnknownFrame duplicate(); 49 | 50 | @Override 51 | Http3UnknownFrame retainedDuplicate(); 52 | 53 | @Override 54 | Http3UnknownFrame replace(ByteBuf content); 55 | 56 | @Override 57 | Http3UnknownFrame retain(); 58 | 59 | @Override 60 | Http3UnknownFrame retain(int increment); 61 | 62 | @Override 63 | Http3UnknownFrame touch(); 64 | 65 | @Override 66 | Http3UnknownFrame touch(Object hint); 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/NotNullByDefault.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | 19 | import org.jetbrains.annotations.NotNull; 20 | 21 | import javax.annotation.meta.TypeQualifierDefault; 22 | import java.lang.annotation.Documented; 23 | import java.lang.annotation.ElementType; 24 | import java.lang.annotation.Retention; 25 | import java.lang.annotation.RetentionPolicy; 26 | import java.lang.annotation.Target; 27 | 28 | /** 29 | * Specifies that all parameters and return values of methods within a package should be treated as non-null by 30 | * default, unless explicitly annotated with a nullness annotation such as {@link org.jetbrains.annotations.Nullable}. 31 | *

32 | * This annotation is applied at the package level and affects all types within the annotated package. 33 | *

34 | * Any nullness annotations explicitly applied to a parameter or return value within a package will override 35 | * the default nullness behavior specified by {@link NotNullByDefault}. 36 | */ 37 | @Documented 38 | @TypeQualifierDefault({ ElementType.PARAMETER, ElementType.METHOD }) 39 | @Retention(RetentionPolicy.CLASS) 40 | @Target(ElementType.PACKAGE) 41 | @NotNull 42 | @interface NotNullByDefault { 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/QpackAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.incubator.codec.http3; 18 | 19 | import io.netty.incubator.codec.quic.QuicChannel; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | import io.netty.util.concurrent.Future; 22 | import io.netty.util.concurrent.GenericFutureListener; 23 | import io.netty.util.concurrent.Promise; 24 | 25 | import static java.util.Objects.requireNonNull; 26 | 27 | final class QpackAttributes { 28 | private final QuicChannel channel; 29 | private final boolean dynamicTableDisabled; 30 | private final Promise encoderStreamPromise; 31 | private final Promise decoderStreamPromise; 32 | 33 | private QuicStreamChannel encoderStream; 34 | private QuicStreamChannel decoderStream; 35 | 36 | QpackAttributes(QuicChannel channel, boolean disableDynamicTable) { 37 | this.channel = channel; 38 | dynamicTableDisabled = disableDynamicTable; 39 | encoderStreamPromise = dynamicTableDisabled ? null : channel.eventLoop().newPromise(); 40 | decoderStreamPromise = dynamicTableDisabled ? null : channel.eventLoop().newPromise(); 41 | } 42 | 43 | boolean dynamicTableDisabled() { 44 | return dynamicTableDisabled; 45 | } 46 | 47 | boolean decoderStreamAvailable() { 48 | return !dynamicTableDisabled && decoderStream != null; 49 | } 50 | 51 | boolean encoderStreamAvailable() { 52 | return !dynamicTableDisabled && encoderStream != null; 53 | } 54 | 55 | void whenEncoderStreamAvailable(GenericFutureListener> listener) { 56 | assert !dynamicTableDisabled; 57 | assert encoderStreamPromise != null; 58 | encoderStreamPromise.addListener(listener); 59 | } 60 | 61 | void whenDecoderStreamAvailable(GenericFutureListener> listener) { 62 | assert !dynamicTableDisabled; 63 | assert decoderStreamPromise != null; 64 | decoderStreamPromise.addListener(listener); 65 | } 66 | 67 | QuicStreamChannel decoderStream() { 68 | assert decoderStreamAvailable(); 69 | return decoderStream; 70 | } 71 | 72 | QuicStreamChannel encoderStream() { 73 | assert encoderStreamAvailable(); 74 | return encoderStream; 75 | } 76 | 77 | void decoderStream(QuicStreamChannel decoderStream) { 78 | assert channel.eventLoop().inEventLoop(); 79 | assert !dynamicTableDisabled; 80 | assert decoderStreamPromise != null; 81 | assert this.decoderStream == null; 82 | this.decoderStream = requireNonNull(decoderStream); 83 | decoderStreamPromise.setSuccess(decoderStream); 84 | } 85 | 86 | void encoderStream(QuicStreamChannel encoderStream) { 87 | assert channel.eventLoop().inEventLoop(); 88 | assert !dynamicTableDisabled; 89 | assert encoderStreamPromise != null; 90 | assert this.encoderStream == null; 91 | this.encoderStream = requireNonNull(encoderStream); 92 | encoderStreamPromise.setSuccess(encoderStream); 93 | } 94 | 95 | void encoderStreamInactive(Throwable cause) { 96 | assert channel.eventLoop().inEventLoop(); 97 | assert !dynamicTableDisabled; 98 | assert encoderStreamPromise != null; 99 | encoderStreamPromise.tryFailure(cause); 100 | } 101 | 102 | void decoderStreamInactive(Throwable cause) { 103 | assert channel.eventLoop().inEventLoop(); 104 | assert !dynamicTableDisabled; 105 | assert decoderStreamPromise != null; 106 | decoderStreamPromise.tryFailure(cause); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/QpackDecoderDynamicTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.incubator.codec.http3; 18 | 19 | import java.util.Arrays; 20 | 21 | import static io.netty.incubator.codec.http3.QpackHeaderField.ENTRY_OVERHEAD; 22 | import static io.netty.incubator.codec.http3.QpackUtil.MAX_HEADER_TABLE_SIZE; 23 | import static io.netty.incubator.codec.http3.QpackUtil.MIN_HEADER_TABLE_SIZE; 24 | import static io.netty.incubator.codec.http3.QpackUtil.toIntOrThrow; 25 | import static java.lang.Math.floorDiv; 26 | 27 | final class QpackDecoderDynamicTable { 28 | private static final QpackException GET_ENTRY_ILLEGAL_INDEX_VALUE = 29 | QpackException.newStatic(QpackDecoderDynamicTable.class, "getEntry(...)", 30 | "QPACK - illegal decoder dynamic table index value"); 31 | private static final QpackException HEADER_TOO_LARGE = 32 | QpackException.newStatic(QpackDecoderDynamicTable.class, "add(...)", "QPACK - header entry too large."); 33 | 34 | // a circular queue of header fields 35 | private QpackHeaderField[] fields; 36 | private int head; 37 | private int tail; 38 | private long size; 39 | private long capacity = -1; // ensure setCapacity creates the array 40 | private int insertCount; 41 | 42 | int length() { 43 | return head < tail ? fields.length - tail + head : head - tail; 44 | } 45 | 46 | long size() { 47 | return size; 48 | } 49 | 50 | int insertCount() { 51 | return insertCount; 52 | } 53 | 54 | QpackHeaderField getEntry(int index) throws QpackException { 55 | if (index < 0 || fields == null || index >= fields.length) { 56 | throw GET_ENTRY_ILLEGAL_INDEX_VALUE; 57 | } 58 | QpackHeaderField entry = fields[index]; 59 | if (entry == null) { 60 | throw GET_ENTRY_ILLEGAL_INDEX_VALUE; 61 | } 62 | return entry; 63 | } 64 | 65 | QpackHeaderField getEntryRelativeEncodedField(int index) throws QpackException { 66 | // https://www.rfc-editor.org/rfc/rfc9204.html#name-relative-indexing 67 | return getEntry(moduloIndex(index)); 68 | } 69 | 70 | QpackHeaderField getEntryRelativeEncoderInstructions(int index) throws QpackException { 71 | // https://www.rfc-editor.org/rfc/rfc9204.html#name-relative-indexing 72 | // Name index is the relative index, relative to the last added entry. 73 | return getEntry(index > tail ? fields.length - index + tail : tail - index); 74 | } 75 | 76 | void add(QpackHeaderField header) throws QpackException { 77 | long headerSize = header.size(); 78 | if (headerSize > capacity) { 79 | throw HEADER_TOO_LARGE; 80 | } 81 | while (capacity - size < headerSize) { 82 | remove(); 83 | } 84 | insertCount++; 85 | fields[getAndIncrementHead()] = header; 86 | size += headerSize; 87 | } 88 | 89 | private void remove() { 90 | QpackHeaderField removed = fields[tail]; 91 | if (removed == null) { 92 | return; 93 | } 94 | size -= removed.size(); 95 | fields[getAndIncrementTail()] = null; 96 | } 97 | 98 | void clear() { 99 | if (fields != null) { 100 | Arrays.fill(fields, null); 101 | } 102 | head = 0; 103 | tail = 0; 104 | size = 0; 105 | } 106 | 107 | void setCapacity(long capacity) throws QpackException { 108 | if (capacity < MIN_HEADER_TABLE_SIZE || capacity > MAX_HEADER_TABLE_SIZE) { 109 | throw new IllegalArgumentException("capacity is invalid: " + capacity); 110 | } 111 | // initially capacity will be -1 so init won't return here 112 | if (this.capacity == capacity) { 113 | return; 114 | } 115 | this.capacity = capacity; 116 | 117 | if (capacity == 0) { 118 | clear(); 119 | } else { 120 | // initially size will be 0 so remove won't be called 121 | while (size > capacity) { 122 | remove(); 123 | } 124 | } 125 | 126 | int maxEntries = toIntOrThrow(2 * floorDiv(capacity, ENTRY_OVERHEAD)); 127 | 128 | // check if capacity change requires us to reallocate the array 129 | if (fields != null && fields.length == maxEntries) { 130 | return; 131 | } 132 | 133 | QpackHeaderField[] tmp = new QpackHeaderField[maxEntries]; 134 | 135 | // initially length will be 0 so there will be no copy 136 | int len = length(); 137 | if (fields != null && tail != head) { 138 | if (head > tail) { 139 | System.arraycopy(fields, tail, tmp, 0, head - tail); 140 | } else { 141 | System.arraycopy(fields, 0, tmp, 0, head); 142 | System.arraycopy(fields, tail, tmp, head, fields.length - tail); 143 | } 144 | } 145 | 146 | tail = 0; 147 | head = tail + len; 148 | fields = tmp; 149 | } 150 | 151 | private int getAndIncrementHead() { 152 | int val = this.head; 153 | this.head = safeIncrementIndex(val); 154 | return val; 155 | } 156 | 157 | private int getAndIncrementTail() { 158 | int val = this.tail; 159 | this.tail = safeIncrementIndex(val); 160 | return val; 161 | } 162 | 163 | private int safeIncrementIndex(int index) { 164 | return ++index % fields.length; 165 | } 166 | 167 | private int moduloIndex(int index) { 168 | return fields == null ? index : index % fields.length; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/QpackDecoderStateSyncStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.incubator.codec.http3; 18 | 19 | /** 20 | * A strategy that determines when to send acknowledgment of new table 22 | * entries on the QPACK decoder stream. 23 | */ 24 | public interface QpackDecoderStateSyncStrategy { 25 | 26 | /** 27 | * Callback when an 29 | * encoded header field section is decoded successfully by the decoder. 30 | * 31 | * @param requiredInsertCount for the encoded field section. 32 | */ 33 | void sectionAcknowledged(int requiredInsertCount); 34 | 35 | /** 36 | * When a header field entry is added to the decoder dynamic table. 37 | * 38 | * @param insertCount for the entry. 39 | * @return {@code true} if an insert count 41 | * increment decoder instruction should be sent. 42 | */ 43 | boolean entryAdded(int insertCount); 44 | 45 | /** 46 | * Returns a {@link QpackDecoderStateSyncStrategy} that will acknowledge each entry added via 47 | * {@link #entryAdded(int)} unless a prior {@link #sectionAcknowledged(int)} call has implicitly acknowledged the 48 | * addition. 49 | * 50 | * @return A {@link QpackDecoderStateSyncStrategy} that will acknowledge each entry added via 51 | * {@link #entryAdded(int)} unless a prior {@link #sectionAcknowledged(int)} call has implicitly acknowledged the 52 | * addition. 53 | */ 54 | static QpackDecoderStateSyncStrategy ackEachInsert() { 55 | return new QpackDecoderStateSyncStrategy() { 56 | private int lastCountAcknowledged; 57 | 58 | @Override 59 | public void sectionAcknowledged(int requiredInsertCount) { 60 | if (lastCountAcknowledged < requiredInsertCount) { 61 | lastCountAcknowledged = requiredInsertCount; 62 | } 63 | } 64 | 65 | @Override 66 | public boolean entryAdded(int insertCount) { 67 | if (lastCountAcknowledged < insertCount) { 68 | lastCountAcknowledged = insertCount; 69 | return true; 70 | } 71 | return false; 72 | } 73 | }; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/QpackException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.util.internal.ThrowableUtil; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | /** 22 | * Exception thrown if an error happens during QPACK processing. 23 | */ 24 | public final class QpackException extends Exception { 25 | 26 | private QpackException(String message, @Nullable Throwable cause, boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | 31 | static QpackException newStatic(Class clazz, String method, String message) { 32 | return ThrowableUtil.unknownStackTrace(new QpackException(message, null, false, false), clazz, method); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/QpackHeaderField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import static io.netty.util.internal.ObjectUtil.checkNotNull; 19 | 20 | class QpackHeaderField { 21 | 22 | /** 23 | * 24 | * Section 3.2.1 Dynamic Table Size. 25 | * The size of an entry is the sum of its name's length in bytes, its 26 | * value's length in bytes, and 32. 27 | */ 28 | static final int ENTRY_OVERHEAD = 32; 29 | 30 | static long sizeOf(CharSequence name, CharSequence value) { 31 | return name.length() + value.length() + ENTRY_OVERHEAD; 32 | } 33 | 34 | final CharSequence name; 35 | final CharSequence value; 36 | 37 | // This constructor can only be used if name and value are ISO-8859-1 encoded. 38 | QpackHeaderField(CharSequence name, CharSequence value) { 39 | this.name = checkNotNull(name, "name"); 40 | this.value = checkNotNull(value, "value"); 41 | } 42 | 43 | long size() { 44 | return sizeOf(name, value); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return name + ": " + value; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/QpackHuffmanEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.util.AsciiString; 20 | import io.netty.util.ByteProcessor; 21 | import io.netty.util.internal.ObjectUtil; 22 | 23 | final class QpackHuffmanEncoder { 24 | 25 | private final int[] codes; 26 | private final byte[] lengths; 27 | private final EncodedLengthProcessor encodedLengthProcessor = new EncodedLengthProcessor(); 28 | private final EncodeProcessor encodeProcessor = new EncodeProcessor(); 29 | 30 | QpackHuffmanEncoder() { 31 | this(QpackUtil.HUFFMAN_CODES, QpackUtil.HUFFMAN_CODE_LENGTHS); 32 | } 33 | 34 | /** 35 | * Creates a new Huffman encoder with the specified Huffman coding. 36 | * 37 | * @param codes the Huffman codes indexed by symbol 38 | * @param lengths the length of each Huffman code 39 | */ 40 | private QpackHuffmanEncoder(int[] codes, byte[] lengths) { 41 | this.codes = codes; 42 | this.lengths = lengths; 43 | } 44 | 45 | /** 46 | * Compresses the input string literal using the Huffman coding. 47 | * 48 | * @param out the output stream for the compressed data 49 | * @param data the string literal to be Huffman encoded 50 | */ 51 | public void encode(ByteBuf out, CharSequence data) { 52 | ObjectUtil.checkNotNull(out, "out"); 53 | if (data instanceof AsciiString) { 54 | AsciiString string = (AsciiString) data; 55 | try { 56 | encodeProcessor.out = out; 57 | string.forEachByte(encodeProcessor); 58 | } catch (Exception e) { 59 | throw new IllegalStateException(e); 60 | } finally { 61 | encodeProcessor.end(); 62 | } 63 | } else { 64 | encodeSlowPath(out, data); 65 | } 66 | } 67 | 68 | private void encodeSlowPath(ByteBuf out, CharSequence data) { 69 | long current = 0; 70 | int n = 0; 71 | 72 | for (int i = 0; i < data.length(); i++) { 73 | int b = data.charAt(i) & 0xFF; 74 | int code = codes[b]; 75 | int nbits = lengths[b]; 76 | 77 | current <<= nbits; 78 | current |= code; 79 | n += nbits; 80 | 81 | while (n >= 8) { 82 | n -= 8; 83 | out.writeByte((int) (current >> n)); 84 | } 85 | } 86 | 87 | if (n > 0) { 88 | current <<= 8 - n; 89 | current |= 0xFF >>> n; // this should be EOS symbol 90 | out.writeByte((int) current); 91 | } 92 | } 93 | 94 | /** 95 | * Returns the number of bytes required to Huffman encode the input string literal. 96 | * 97 | * @param data the string literal to be Huffman encoded 98 | * @return the number of bytes required to Huffman encode {@code data} 99 | */ 100 | int getEncodedLength(CharSequence data) { 101 | if (data instanceof AsciiString) { 102 | AsciiString string = (AsciiString) data; 103 | try { 104 | encodedLengthProcessor.reset(); 105 | string.forEachByte(encodedLengthProcessor); 106 | return encodedLengthProcessor.length(); 107 | } catch (Exception e) { 108 | throw new IllegalStateException(e); 109 | } 110 | } else { 111 | return getEncodedLengthSlowPath(data); 112 | } 113 | } 114 | 115 | private int getEncodedLengthSlowPath(CharSequence data) { 116 | long len = 0; 117 | for (int i = 0; i < data.length(); i++) { 118 | len += lengths[data.charAt(i) & 0xFF]; 119 | } 120 | return (int) ((len + 7) >> 3); 121 | } 122 | 123 | private final class EncodeProcessor implements ByteProcessor { 124 | ByteBuf out; 125 | private long current; 126 | private int n; 127 | 128 | @Override 129 | public boolean process(byte value) { 130 | int b = value & 0xFF; 131 | int nbits = lengths[b]; 132 | 133 | current <<= nbits; 134 | current |= codes[b]; 135 | n += nbits; 136 | 137 | while (n >= 8) { 138 | n -= 8; 139 | out.writeByte((int) (current >> n)); 140 | } 141 | return true; 142 | } 143 | 144 | void end() { 145 | try { 146 | if (n > 0) { 147 | current <<= 8 - n; 148 | current |= 0xFF >>> n; // this should be EOS symbol 149 | out.writeByte((int) current); 150 | } 151 | } finally { 152 | out = null; 153 | current = 0; 154 | n = 0; 155 | } 156 | } 157 | } 158 | 159 | private final class EncodedLengthProcessor implements ByteProcessor { 160 | private long len; 161 | 162 | @Override 163 | public boolean process(byte value) { 164 | len += lengths[value & 0xFF]; 165 | return true; 166 | } 167 | 168 | void reset() { 169 | len = 0; 170 | } 171 | 172 | int length() { 173 | return (int) ((len + 7) >> 3); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/io/netty/incubator/codec/http3/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * HTTP/3 implementation. 19 | */ 20 | 21 | @NotNullByDefault 22 | package io.netty.incubator.codec.http3; 23 | 24 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/AbtractHttp3ConnectionHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelDuplexHandler; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | import io.netty.incubator.codec.quic.QuicStreamType; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertNotNull; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | public abstract class AbtractHttp3ConnectionHandlerTest { 28 | 29 | private final boolean server; 30 | 31 | protected abstract Http3ConnectionHandler newConnectionHandler(); 32 | 33 | protected abstract void assertBidirectionalStreamHandled(EmbeddedQuicChannel channel, 34 | QuicStreamChannel streamChannel); 35 | 36 | public AbtractHttp3ConnectionHandlerTest(boolean server) { 37 | this.server = server; 38 | } 39 | 40 | @Test 41 | public void testOpenLocalControlStream() throws Exception { 42 | EmbeddedQuicChannel quicChannel = new EmbeddedQuicChannel(server, new ChannelDuplexHandler()); 43 | ChannelHandlerContext ctx = quicChannel.pipeline().firstContext(); 44 | 45 | Http3ConnectionHandler handler = newConnectionHandler(); 46 | handler.handlerAdded(ctx); 47 | handler.channelRegistered(ctx); 48 | handler.channelActive(ctx); 49 | 50 | final EmbeddedQuicStreamChannel localControlStream = quicChannel.localControlStream(); 51 | assertNotNull(localControlStream); 52 | 53 | assertNotNull(Http3.getLocalControlStream(quicChannel)); 54 | 55 | handler.channelInactive(ctx); 56 | handler.channelUnregistered(ctx); 57 | handler.handlerRemoved(ctx); 58 | 59 | assertTrue(localControlStream.finishAndReleaseAll()); 60 | } 61 | 62 | @Test 63 | public void testBidirectionalStream() throws Exception { 64 | EmbeddedQuicChannel quicChannel = new EmbeddedQuicChannel(server, new ChannelDuplexHandler()); 65 | final EmbeddedQuicStreamChannel bidirectionalStream = 66 | (EmbeddedQuicStreamChannel) quicChannel.createStream(QuicStreamType.BIDIRECTIONAL, 67 | new ChannelDuplexHandler()).get(); 68 | ChannelHandlerContext ctx = quicChannel.pipeline().firstContext(); 69 | 70 | Http3ConnectionHandler handler = newConnectionHandler(); 71 | handler.handlerAdded(ctx); 72 | handler.channelRegistered(ctx); 73 | handler.channelActive(ctx); 74 | 75 | final EmbeddedQuicStreamChannel localControlStream = quicChannel.localControlStream(); 76 | assertNotNull(localControlStream); 77 | 78 | handler.channelRead(ctx, bidirectionalStream); 79 | 80 | assertBidirectionalStreamHandled(quicChannel, bidirectionalStream); 81 | handler.channelInactive(ctx); 82 | handler.channelUnregistered(ctx); 83 | handler.handlerRemoved(ctx); 84 | 85 | assertTrue(localControlStream.finishAndReleaseAll()); 86 | } 87 | 88 | @Test 89 | public void testUnidirectionalStream() throws Exception { 90 | EmbeddedQuicChannel quicChannel = new EmbeddedQuicChannel(server, new ChannelDuplexHandler()); 91 | final QuicStreamChannel unidirectionalStream = 92 | quicChannel.createStream(QuicStreamType.UNIDIRECTIONAL, new ChannelDuplexHandler()).get(); 93 | ChannelHandlerContext ctx = quicChannel.pipeline().firstContext(); 94 | 95 | Http3ConnectionHandler handler = newConnectionHandler(); 96 | handler.handlerAdded(ctx); 97 | handler.channelRegistered(ctx); 98 | handler.channelActive(ctx); 99 | 100 | final EmbeddedQuicStreamChannel localControlStream = quicChannel.localControlStream(); 101 | assertNotNull(localControlStream); 102 | 103 | handler.channelRead(ctx, unidirectionalStream); 104 | 105 | assertNotNull(unidirectionalStream.pipeline().get(Http3UnidirectionalStreamInboundHandler.class)); 106 | 107 | handler.channelInactive(ctx); 108 | handler.channelUnregistered(ctx); 109 | handler.handlerRemoved(ctx); 110 | 111 | assertTrue(localControlStream.finishAndReleaseAll()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3ClientConnectionHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.incubator.codec.quic.QuicStreamChannel; 19 | 20 | public class Http3ClientConnectionHandlerTest extends AbtractHttp3ConnectionHandlerTest { 21 | 22 | public Http3ClientConnectionHandlerTest() { 23 | super(false); 24 | } 25 | 26 | @Override 27 | protected Http3ConnectionHandler newConnectionHandler() { 28 | return new Http3ClientConnectionHandler(); 29 | } 30 | 31 | @Override 32 | protected void assertBidirectionalStreamHandled(EmbeddedQuicChannel channel, QuicStreamChannel streamChannel) { 33 | Http3TestUtils.verifyClose(Http3ErrorCode.H3_STREAM_CREATION_ERROR, channel); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3ControlStreamFrameTypeValidatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | public class Http3ControlStreamFrameTypeValidatorTest extends Http3FrameTypeValidatorTest { 19 | 20 | @Override 21 | protected long[] invalidFramesTypes() { 22 | return new long[] { 23 | Http3CodecUtils.HTTP3_DATA_FRAME_TYPE, 24 | Http3CodecUtils.HTTP3_HEADERS_FRAME_TYPE, 25 | Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE 26 | }; 27 | } 28 | 29 | @Override 30 | protected long[] validFrameTypes() { 31 | return new long[] { 32 | Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE, 33 | Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE, 34 | Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE, 35 | Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE 36 | }; 37 | } 38 | 39 | @Override 40 | protected Http3FrameTypeValidator newValidator() { 41 | return Http3ControlStreamFrameTypeValidator.INSTANCE; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3FrameTypeValidationHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.incubator.codec.quic.QuicStreamType; 20 | 21 | import java.util.Arrays; 22 | import java.util.Collections; 23 | import java.util.List; 24 | 25 | abstract class Http3FrameTypeValidationHandlerTest extends 26 | AbstractHttp3FrameTypeValidationHandlerTest { 27 | 28 | Http3FrameTypeValidationHandlerTest(boolean isInbound, boolean isOutbound) { 29 | super(QuicStreamType.BIDIRECTIONAL, isInbound, isOutbound); 30 | } 31 | 32 | @Override 33 | protected ChannelHandler newHandler(boolean server) { 34 | return new Http3FrameTypeDuplexValidationHandler<>(Http3RequestStreamFrame.class); 35 | } 36 | 37 | @Override 38 | protected List newValidFrames() { 39 | return Collections.singletonList(Http3TestUtils.newHttp3RequestStreamFrame()); 40 | } 41 | 42 | @Override 43 | protected List newInvalidFrames() { 44 | return Arrays.asList(Http3TestUtils.newHttp3ControlStreamFrame(), Http3TestUtils.newHttp3PushStreamFrame()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3FrameTypeValidatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import org.junit.jupiter.api.Test; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertThrows; 21 | 22 | public abstract class Http3FrameTypeValidatorTest { 23 | 24 | protected abstract long[] invalidFramesTypes(); 25 | protected abstract long[] validFrameTypes(); 26 | 27 | protected abstract Http3FrameTypeValidator newValidator(); 28 | 29 | @Test 30 | public void testValidFrameTypes() throws Exception { 31 | for (long validFrameType: validFrameTypes()) { 32 | newValidator().validate(validFrameType, true); 33 | } 34 | } 35 | 36 | @Test 37 | public void testInvalidFrameTypes() { 38 | for (long invalidFrameType: invalidFramesTypes()) { 39 | assertThrows(Http3Exception.class, () -> newValidator().validate(invalidFrameType, true)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3PushStreamFrameTypeValidatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | public class Http3PushStreamFrameTypeValidatorTest extends Http3FrameTypeValidatorTest { 19 | 20 | @Override 21 | protected long[] invalidFramesTypes() { 22 | return new long[] { 23 | Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE, 24 | Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE, 25 | Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE, 26 | Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE, 27 | Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE 28 | }; 29 | } 30 | 31 | @Override 32 | protected long[] validFrameTypes() { 33 | return new long[] { 34 | Http3CodecUtils.HTTP3_DATA_FRAME_TYPE, 35 | Http3CodecUtils.HTTP3_HEADERS_FRAME_TYPE 36 | }; 37 | } 38 | 39 | @Override 40 | protected Http3FrameTypeValidator newValidator() { 41 | return Http3PushStreamFrameTypeValidator.INSTANCE; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3PushStreamServerValidationHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.Unpooled; 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.incubator.codec.quic.QuicStreamType; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | public class Http3PushStreamServerValidationHandlerTest extends 26 | AbstractHttp3FrameTypeValidationHandlerTest { 27 | 28 | public Http3PushStreamServerValidationHandlerTest() { 29 | super(QuicStreamType.UNIDIRECTIONAL, false, true); 30 | } 31 | 32 | @Override 33 | protected ChannelHandler newHandler(boolean server) { 34 | return Http3PushStreamServerValidationHandler.INSTANCE; 35 | } 36 | 37 | @Override 38 | protected List newValidFrames() { 39 | return Arrays.asList(new DefaultHttp3HeadersFrame(), new DefaultHttp3DataFrame(Unpooled.EMPTY_BUFFER)); 40 | } 41 | 42 | @Override 43 | protected List newInvalidFrames() { 44 | return Arrays.asList(Http3TestUtils.newHttp3RequestStreamFrame(), Http3TestUtils.newHttp3ControlStreamFrame()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3RequestStreamFrameTypeValidatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | public class Http3RequestStreamFrameTypeValidatorTest extends Http3FrameTypeValidatorTest { 19 | 20 | @Override 21 | protected long[] invalidFramesTypes() { 22 | return new long[] { 23 | Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE, 24 | Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE, 25 | Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE, 26 | Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE 27 | }; 28 | } 29 | 30 | @Override 31 | protected long[] validFrameTypes() { 32 | return new long[] { 33 | Http3CodecUtils.HTTP3_DATA_FRAME_TYPE, 34 | Http3CodecUtils.HTTP3_HEADERS_FRAME_TYPE, 35 | Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE 36 | }; 37 | } 38 | 39 | @Override 40 | protected Http3FrameTypeValidator newValidator() { 41 | return Http3RequestStreamFrameTypeValidator.INSTANCE; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3RequestStreamInboundHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.Unpooled; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.embedded.EmbeddedChannel; 21 | import io.netty.channel.socket.ChannelInputShutdownEvent; 22 | import io.netty.util.ReferenceCountUtil; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertFalse; 27 | import static org.junit.jupiter.api.Assertions.assertNotNull; 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | public class Http3RequestStreamInboundHandlerTest { 31 | 32 | @Test 33 | public void testDetectLastViaUserEvent() { 34 | EmbeddedQuicStreamChannel channel = new EmbeddedQuicStreamChannel(new TestHttp3RequestStreamInboundHandler()); 35 | assertTrue(channel.writeInbound(new DefaultHttp3HeadersFrame())); 36 | assertTrue(channel.writeInbound(new DefaultHttp3DataFrame(Unpooled.buffer()))); 37 | assertTrue(channel.writeInbound(new DefaultHttp3DataFrame(Unpooled.buffer()))); 38 | channel.pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE); 39 | assertFrame(channel); 40 | assertFrame(channel); 41 | assertFrame(channel); 42 | assertEquals(true, channel.readInbound()); 43 | assertFalse(channel.finish()); 44 | } 45 | 46 | private void assertFrame(EmbeddedChannel channel) { 47 | Http3Frame frame = channel.readInbound(); 48 | assertNotNull(frame); 49 | ReferenceCountUtil.release(frame); 50 | } 51 | 52 | private static final class TestHttp3RequestStreamInboundHandler extends Http3RequestStreamInboundHandler { 53 | 54 | @Override 55 | public void channelRead(ChannelHandlerContext ctx, Http3HeadersFrame frame) { 56 | ctx.fireChannelRead(frame); 57 | } 58 | 59 | @Override 60 | public void channelRead(ChannelHandlerContext ctx, Http3DataFrame frame) { 61 | ctx.fireChannelRead(frame); 62 | } 63 | 64 | @Override 65 | protected void channelInputClosed(ChannelHandlerContext ctx) { 66 | ctx.fireChannelRead(true); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3ServerConnectionHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertNotNull; 23 | 24 | public class Http3ServerConnectionHandlerTest extends AbtractHttp3ConnectionHandlerTest { 25 | private static final ChannelHandler REQUEST_HANDLER = new ChannelInboundHandlerAdapter() { 26 | @Override 27 | public boolean isSharable() { 28 | return true; 29 | } 30 | }; 31 | 32 | public Http3ServerConnectionHandlerTest() { 33 | super(true); 34 | } 35 | 36 | @Override 37 | protected Http3ConnectionHandler newConnectionHandler() { 38 | return new Http3ServerConnectionHandler(REQUEST_HANDLER); 39 | } 40 | 41 | @Override 42 | protected void assertBidirectionalStreamHandled(EmbeddedQuicChannel channel, QuicStreamChannel streamChannel) { 43 | assertNotNull(streamChannel.pipeline().context(REQUEST_HANDLER)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3SpecTestServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.bootstrap.Bootstrap; 19 | import io.netty.buffer.Unpooled; 20 | import io.netty.channel.Channel; 21 | import io.netty.channel.ChannelHandler; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.channel.ChannelInitializer; 24 | import io.netty.channel.nio.NioEventLoopGroup; 25 | import io.netty.channel.socket.nio.NioDatagramChannel; 26 | import io.netty.handler.ssl.util.SelfSignedCertificate; 27 | import io.netty.incubator.codec.quic.InsecureQuicTokenHandler; 28 | import io.netty.incubator.codec.quic.QuicChannel; 29 | import io.netty.incubator.codec.quic.QuicSslContext; 30 | import io.netty.incubator.codec.quic.QuicSslContextBuilder; 31 | import io.netty.incubator.codec.quic.QuicStreamChannel; 32 | import io.netty.util.CharsetUtil; 33 | import io.netty.util.ReferenceCountUtil; 34 | 35 | import java.net.InetSocketAddress; 36 | import java.util.concurrent.TimeUnit; 37 | 38 | public final class Http3SpecTestServer { 39 | private static final byte[] CONTENT = "Hello World!\r\n".getBytes(CharsetUtil.US_ASCII); 40 | static final int PORT = 9999; 41 | 42 | private Http3SpecTestServer() { } 43 | 44 | public static void main(String... args) throws Exception { 45 | int port; 46 | // Allow to pass in the port so we can also use it to run h3spec against 47 | if (args.length == 1) { 48 | port = Integer.parseInt(args[0]); 49 | } else { 50 | port = PORT; 51 | } 52 | NioEventLoopGroup group = new NioEventLoopGroup(1); 53 | SelfSignedCertificate cert = new SelfSignedCertificate(); 54 | QuicSslContext sslContext = QuicSslContextBuilder.forServer(cert.key(), null, cert.cert()) 55 | .applicationProtocols(Http3.supportedApplicationProtocols()).build(); 56 | ChannelHandler codec = Http3.newQuicServerCodecBuilder() 57 | .sslContext(sslContext) 58 | .maxIdleTimeout(5000, TimeUnit.MILLISECONDS) 59 | .initialMaxData(10000000) 60 | .initialMaxStreamDataBidirectionalLocal(1000000) 61 | .initialMaxStreamDataBidirectionalRemote(1000000) 62 | .initialMaxStreamsBidirectional(100) 63 | .tokenHandler(InsecureQuicTokenHandler.INSTANCE) 64 | .handler(new ChannelInitializer() { 65 | @Override 66 | protected void initChannel(QuicChannel ch) { 67 | // Called for each connection 68 | ch.pipeline().addLast(new Http3ServerConnectionHandler( 69 | new ChannelInitializer() { 70 | // Called for each request-stream, 71 | @Override 72 | protected void initChannel(QuicStreamChannel ch) { 73 | ch.pipeline().addLast(new Http3RequestStreamInboundHandler() { 74 | 75 | @Override 76 | protected void channelRead( 77 | ChannelHandlerContext ctx, Http3HeadersFrame frame) { 78 | 79 | ReferenceCountUtil.release(frame); 80 | } 81 | 82 | @Override 83 | protected void channelRead( 84 | ChannelHandlerContext ctx, Http3DataFrame frame) { 85 | ReferenceCountUtil.release(frame); 86 | } 87 | 88 | @Override 89 | protected void channelInputClosed(ChannelHandlerContext ctx) { 90 | Http3HeadersFrame headersFrame = new DefaultHttp3HeadersFrame(); 91 | headersFrame.headers().status("404"); 92 | headersFrame.headers().add("server", "netty"); 93 | headersFrame.headers().addInt("content-length", CONTENT.length); 94 | ctx.write(headersFrame); 95 | ctx.writeAndFlush(new DefaultHttp3DataFrame( 96 | Unpooled.wrappedBuffer(CONTENT))) 97 | .addListener(QuicStreamChannel.SHUTDOWN_OUTPUT); 98 | } 99 | }); 100 | } 101 | }, null, null, null, true)); 102 | } 103 | }).build(); 104 | try { 105 | Bootstrap bs = new Bootstrap(); 106 | Channel channel = bs.group(group) 107 | .channel(NioDatagramChannel.class) 108 | .handler(codec) 109 | .bind(new InetSocketAddress(port)).sync().channel(); 110 | channel.closeFuture().sync(); 111 | } finally { 112 | group.shutdownGracefully(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/Http3TestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.util.ReferenceCountUtil; 20 | import io.netty.util.ReferenceCounted; 21 | import org.hamcrest.CoreMatchers; 22 | import org.hamcrest.MatcherAssert; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | import static org.junit.jupiter.api.Assertions.assertSame; 26 | 27 | final class Http3TestUtils { 28 | 29 | private Http3TestUtils() { } 30 | 31 | static void assertException(Http3ErrorCode code, Throwable e) { 32 | MatcherAssert.assertThat(e, CoreMatchers.instanceOf(Http3Exception.class)); 33 | Http3Exception exception = (Http3Exception) e; 34 | assertEquals(code, exception.errorCode()); 35 | } 36 | 37 | static void verifyClose(int times, Http3ErrorCode expectedCode, EmbeddedQuicChannel channel) { 38 | assertEquals(times, channel.closeErrorCodes().stream().filter(integer -> integer == expectedCode.code).count(), 39 | "Close not invoked with expected times with error code: " + expectedCode.code); 40 | } 41 | 42 | static void verifyClose(Http3ErrorCode expectedCode, EmbeddedQuicChannel channel) { 43 | verifyClose(1, expectedCode, channel); 44 | } 45 | 46 | static void assertBufferEquals(ByteBuf expected, ByteBuf actual) { 47 | try { 48 | assertEquals(expected, actual); 49 | } finally { 50 | ReferenceCountUtil.release(expected); 51 | ReferenceCountUtil.release(actual); 52 | } 53 | } 54 | 55 | static void assertFrameEquals(Http3Frame expected, Http3Frame actual) { 56 | try { 57 | assertEquals(expected, actual); 58 | } finally { 59 | ReferenceCountUtil.release(expected); 60 | ReferenceCountUtil.release(actual); 61 | } 62 | } 63 | 64 | static void assertFrameSame(Http3Frame expected, Http3Frame actual) { 65 | try { 66 | assertSame(expected, actual); 67 | } finally { 68 | // as both frames are the same we only want to release once. 69 | ReferenceCountUtil.release(actual); 70 | } 71 | } 72 | 73 | static void assertFrameReleased(Http3Frame frame) { 74 | if (frame instanceof ReferenceCounted) { 75 | assertEquals(0, ((ReferenceCounted) frame).refCnt()); 76 | } 77 | } 78 | 79 | static Http3Frame newHttp3Frame() { 80 | return () -> 0; 81 | } 82 | 83 | static Http3PushStreamFrame newHttp3PushStreamFrame() { 84 | return () -> 0; 85 | } 86 | 87 | static Http3RequestStreamFrame newHttp3RequestStreamFrame() { 88 | return () -> 0; 89 | } 90 | 91 | static Http3ControlStreamFrame newHttp3ControlStreamFrame() { 92 | return () -> 0; 93 | } 94 | 95 | static DefaultHttp3HeadersFrame newHeadersFrameWithPseudoHeaders() { 96 | final DefaultHttp3HeadersFrame headers = new DefaultHttp3HeadersFrame(); 97 | headers.headers().add(":authority", "netty.quic"); // name only 98 | headers.headers().add(":path", "/"); // name & value 99 | headers.headers().add(":method", "GET"); // name & value with few options per name 100 | headers.headers().add(":scheme", "https"); 101 | return headers; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/QpackDecoderDynamicTableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.incubator.codec.http3; 18 | 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | import static org.junit.jupiter.api.Assertions.assertThrows; 24 | 25 | public class QpackDecoderDynamicTableTest { 26 | 27 | private final QpackHeaderField fooBar = new QpackHeaderField("foo", "bar"); 28 | 29 | @Test 30 | public void length() throws Exception { 31 | QpackDecoderDynamicTable table = newTable(100); 32 | assertEquals(0, table.length()); 33 | table.add(fooBar); 34 | assertEquals(1, table.length()); 35 | table.clear(); 36 | assertEquals(0, table.length()); 37 | } 38 | 39 | @Test 40 | public void size() throws Exception { 41 | QpackDecoderDynamicTable table = newTable(100); 42 | assertEquals(0, table.size()); 43 | QpackHeaderField entry = new QpackHeaderField("foo", "bar"); 44 | table.add(entry); 45 | assertEquals(entry.size(), table.size()); 46 | table.clear(); 47 | assertEquals(0, table.size()); 48 | } 49 | 50 | @Test 51 | public void getEntry() throws Exception { 52 | QpackDecoderDynamicTable table = newTable(100); 53 | QpackHeaderField entry = new QpackHeaderField("foo", "bar"); 54 | table.add(entry); 55 | assertEquals(entry, table.getEntry(0)); 56 | table.clear(); 57 | 58 | assertThrows(QpackException.class, () -> table.getEntry(0)); 59 | } 60 | 61 | @Test 62 | public void getEntryExceptionally() throws Exception { 63 | QpackDecoderDynamicTable table = newTable(1); 64 | 65 | assertThrows(QpackException.class, () -> table.getEntry(0)); 66 | } 67 | 68 | @Test 69 | public void setCapacity() throws Exception { 70 | QpackHeaderField entry1 = new QpackHeaderField("foo", "bar"); 71 | QpackHeaderField entry2 = new QpackHeaderField("hello", "world"); 72 | final long size1 = entry1.size(); 73 | final long size2 = entry2.size(); 74 | QpackDecoderDynamicTable table = newTable(size1 + size2); 75 | table.add(entry1); 76 | table.add(entry2); 77 | assertEquals(2, table.length()); 78 | assertEquals(size1 + size2, table.size()); 79 | assertEquals(entry1, table.getEntry(0)); 80 | assertEquals(entry2, table.getEntry(1)); 81 | 82 | table.setCapacity((size1 + size2) * 2); //larger capacity 83 | assertEquals(2, table.length()); 84 | assertEquals(size1 + size2, table.size()); 85 | 86 | table.setCapacity(size2); //smaller capacity 87 | //entry1 will be removed 88 | assertEquals(1, table.length()); 89 | assertEquals(size2, table.size()); 90 | assertEquals(entry2, table.getEntry(0)); 91 | table.setCapacity(0); //clear all 92 | assertEquals(0, table.length()); 93 | assertEquals(0, table.size()); 94 | } 95 | 96 | @Test 97 | public void add() throws Exception { 98 | QpackDecoderDynamicTable table = newTable(100); 99 | assertEquals(0, table.size()); 100 | QpackHeaderField entry1 = new QpackHeaderField("foo", "bar"); //size:3+3+32=38 101 | QpackHeaderField entry2 = new QpackHeaderField("hello", "world"); 102 | table.add(entry1); //success 103 | assertEquals(entry1.size(), table.size()); 104 | assertEquals(entry1, table.getEntry(0)); 105 | table.setCapacity(32); //entry1 is removed from table 106 | assertEquals(0, table.size()); 107 | assertEquals(0, table.length()); 108 | 109 | table.setCapacity(64); 110 | table.add(entry1); //success 111 | assertEquals(entry1.size(), table.size()); 112 | assertEquals(1, table.length()); 113 | assertEquals(entry1, table.getEntry(0)); 114 | table.add(entry2); //entry2 is added, but entry1 is removed from table 115 | assertEquals(entry2.size(), table.size()); 116 | assertEquals(1, table.length()); 117 | assertEquals(entry2, table.getEntry(1)); 118 | 119 | table.setCapacity(128); 120 | table.add(entry1); //success 121 | assertEquals(entry2, table.getEntry(0)); 122 | } 123 | 124 | private static QpackDecoderDynamicTable newTable(long capacity) throws QpackException { 125 | QpackDecoderDynamicTable table = new QpackDecoderDynamicTable(); 126 | table.setCapacity(capacity); 127 | return table; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/QpackStaticTableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import org.junit.jupiter.api.Test; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertEquals; 21 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 22 | 23 | 24 | public class QpackStaticTableTest { 25 | @Test 26 | public void testFieldNotFound() { 27 | assertEquals(QpackStaticTable.NOT_FOUND, QpackStaticTable.findFieldIndex("x-netty-quic", "incubating")); 28 | } 29 | 30 | @Test 31 | public void testFieldNameAndValueMatch() { 32 | // first in range 33 | assertEquals(15, QpackStaticTable.findFieldIndex(":method", "CONNECT")); 34 | // last in range 35 | assertEquals(21, QpackStaticTable.findFieldIndex(":method", "PUT")); 36 | // non-consequent range 37 | assertEquals(24, QpackStaticTable.findFieldIndex(":status", "103")); 38 | assertEquals(69, QpackStaticTable.findFieldIndex(":status", "421")); 39 | } 40 | 41 | @Test 42 | public void testFieldNameRefForEmptyField() { 43 | int nameIndex1 = QpackStaticTable.findFieldIndex("cookie", "netty.io"); 44 | int nameIndex2 = QpackStaticTable.findFieldIndex("cookie", "quic.io"); 45 | 46 | // should give the same name ref for any values 47 | assertNotEquals(QpackStaticTable.NOT_FOUND, nameIndex1); 48 | assertNotEquals(QpackStaticTable.NOT_FOUND, nameIndex2); 49 | assertEquals(nameIndex1, nameIndex2); 50 | 51 | // index should be masked 52 | assertEquals(nameIndex1 & QpackStaticTable.MASK_NAME_REF, QpackStaticTable.MASK_NAME_REF); 53 | assertEquals(5, nameIndex1 ^ QpackStaticTable.MASK_NAME_REF); 54 | } 55 | 56 | @Test 57 | public void testFieldNameRefForSingleMatch() { 58 | // note the value differs from static table ("1" rather than "0") 59 | int nameIndex = QpackStaticTable.findFieldIndex("age", "1"); 60 | assertEquals(2, nameIndex ^ QpackStaticTable.MASK_NAME_REF); 61 | } 62 | 63 | @Test 64 | public void testFieldNameRefForMultipleMatches() { 65 | int nameIndex = QpackStaticTable.findFieldIndex(":method", "ALLTHETHINGS"); 66 | assertEquals(15, nameIndex ^ QpackStaticTable.MASK_NAME_REF); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/QpackStreamHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.Unpooled; 20 | import io.netty.incubator.codec.quic.QuicStreamType; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static io.netty.incubator.codec.http3.Http3TestUtils.verifyClose; 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | import static org.junit.jupiter.api.Assertions.assertFalse; 26 | 27 | public class QpackStreamHandlerTest { 28 | 29 | @Test 30 | public void testStreamClosedWhileParentStillActive() throws Exception { 31 | EmbeddedQuicChannel parent = new EmbeddedQuicChannel(true); 32 | 33 | EmbeddedQuicStreamChannel channel = 34 | (EmbeddedQuicStreamChannel) parent.createStream(QuicStreamType.UNIDIRECTIONAL, 35 | new QpackDecoderHandler(new QpackEncoder())).get(); 36 | assertFalse(channel.finish()); 37 | verifyClose(1, Http3ErrorCode.H3_CLOSED_CRITICAL_STREAM, parent); 38 | } 39 | 40 | @Test 41 | public void testStreamClosedWhileParentIsInactive() throws Exception { 42 | EmbeddedQuicChannel parent = new EmbeddedQuicChannel(true); 43 | parent.close().get(); 44 | 45 | EmbeddedQuicStreamChannel channel = 46 | (EmbeddedQuicStreamChannel) parent.createStream(QuicStreamType.UNIDIRECTIONAL, 47 | new QpackDecoderHandler(new QpackEncoder())).get(); 48 | assertFalse(channel.finish()); 49 | } 50 | 51 | @Test 52 | public void testStreamDropsInboundData() throws Exception { 53 | EmbeddedQuicChannel parent = new EmbeddedQuicChannel(true); 54 | parent.close().get(); 55 | 56 | EmbeddedQuicStreamChannel channel = 57 | (EmbeddedQuicStreamChannel) parent.createStream(QuicStreamType.UNIDIRECTIONAL, 58 | new QpackDecoderHandler(new QpackEncoder())).get(); 59 | ByteBuf buffer = Unpooled.buffer(); 60 | assertFalse(channel.writeInbound(buffer)); 61 | assertEquals(0, buffer.refCnt()); 62 | assertFalse(channel.finish()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/netty/incubator/codec/http3/example/Http3ClientExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.incubator.codec.http3.example; 17 | 18 | import io.netty.bootstrap.Bootstrap; 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelHandler; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.channel.nio.NioEventLoopGroup; 23 | import io.netty.channel.socket.nio.NioDatagramChannel; 24 | import io.netty.handler.ssl.util.InsecureTrustManagerFactory; 25 | import io.netty.incubator.codec.http3.DefaultHttp3HeadersFrame; 26 | import io.netty.incubator.codec.http3.Http3; 27 | import io.netty.incubator.codec.http3.Http3ClientConnectionHandler; 28 | import io.netty.incubator.codec.http3.Http3DataFrame; 29 | import io.netty.incubator.codec.http3.Http3HeadersFrame; 30 | import io.netty.incubator.codec.http3.Http3RequestStreamInboundHandler; 31 | import io.netty.incubator.codec.quic.QuicChannel; 32 | import io.netty.incubator.codec.quic.QuicSslContext; 33 | import io.netty.incubator.codec.quic.QuicSslContextBuilder; 34 | import io.netty.incubator.codec.quic.QuicStreamChannel; 35 | import io.netty.util.CharsetUtil; 36 | import io.netty.util.NetUtil; 37 | import io.netty.util.ReferenceCountUtil; 38 | 39 | import java.net.InetSocketAddress; 40 | import java.util.concurrent.TimeUnit; 41 | 42 | public final class Http3ClientExample { 43 | private Http3ClientExample() { } 44 | 45 | public static void main(String... args) throws Exception { 46 | NioEventLoopGroup group = new NioEventLoopGroup(1); 47 | 48 | try { 49 | QuicSslContext context = QuicSslContextBuilder.forClient() 50 | .trustManager(InsecureTrustManagerFactory.INSTANCE) 51 | .applicationProtocols(Http3.supportedApplicationProtocols()).build(); 52 | ChannelHandler codec = Http3.newQuicClientCodecBuilder() 53 | .sslContext(context) 54 | .maxIdleTimeout(5000, TimeUnit.MILLISECONDS) 55 | .initialMaxData(10000000) 56 | .initialMaxStreamDataBidirectionalLocal(1000000) 57 | .build(); 58 | 59 | Bootstrap bs = new Bootstrap(); 60 | Channel channel = bs.group(group) 61 | .channel(NioDatagramChannel.class) 62 | .handler(codec) 63 | .bind(0).sync().channel(); 64 | 65 | QuicChannel quicChannel = QuicChannel.newBootstrap(channel) 66 | .handler(new Http3ClientConnectionHandler()) 67 | .remoteAddress(new InetSocketAddress(NetUtil.LOCALHOST4, Http3ServerExample.PORT)) 68 | .connect() 69 | .get(); 70 | 71 | QuicStreamChannel streamChannel = Http3.newRequestStream(quicChannel, 72 | new Http3RequestStreamInboundHandler() { 73 | @Override 74 | protected void channelRead(ChannelHandlerContext ctx, Http3HeadersFrame frame) { 75 | ReferenceCountUtil.release(frame); 76 | } 77 | 78 | @Override 79 | protected void channelRead(ChannelHandlerContext ctx, Http3DataFrame frame) { 80 | System.err.print(frame.content().toString(CharsetUtil.US_ASCII)); 81 | ReferenceCountUtil.release(frame); 82 | } 83 | 84 | @Override 85 | protected void channelInputClosed(ChannelHandlerContext ctx) { 86 | ctx.close(); 87 | } 88 | }).sync().getNow(); 89 | 90 | // Write the Header frame and send the FIN to mark the end of the request. 91 | // After this its not possible anymore to write any more data. 92 | Http3HeadersFrame frame = new DefaultHttp3HeadersFrame(); 93 | frame.headers().method("GET").path("/") 94 | .authority(NetUtil.LOCALHOST4.getHostAddress() + ":" + Http3ServerExample.PORT) 95 | .scheme("https"); 96 | streamChannel.writeAndFlush(frame) 97 | .addListener(QuicStreamChannel.SHUTDOWN_OUTPUT).sync(); 98 | 99 | // Wait for the stream channel and quic channel to be closed (this will happen after we received the FIN). 100 | // After this is done we will close the underlying datagram channel. 101 | streamChannel.closeFuture().sync(); 102 | 103 | // After we received the response lets also close the underlying QUIC channel and datagram channel. 104 | quicChannel.close().sync(); 105 | channel.close().sync(); 106 | } finally { 107 | group.shutdownGracefully(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/resources/cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx 3 | CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl 4 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw 5 | NzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq 6 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs 7 | brqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b 8 | 20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab 9 | +cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn 10 | NuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO 11 | VWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G 12 | CSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV 13 | JV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O 14 | EHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn 15 | TdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz 16 | nNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID 17 | ISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /src/test/resources/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPls4vssP2SIhq 3 | BxWtmpnXdlxHQqEy7OxuupQttkrKoL64FGAg3Fgnss+cngs9i4K3+cBpDsItd8qD 4 | MAwW7x4grpQS9kDr7tvbQTiu86k9tMH9Pglthk1ArIz0ATCvqPQ/q8WCtZPtoHOG 5 | SaKoR7m+//tk4QZv4Bv5y8x6zN8FuT3efLMp01pS6H3nHv+nQ7DfXmenY9G6hPRB 6 | CcD3MT/15nclqHdw1mc25DoEMeWR6DNNXkmFhhuVGj+ma3YT0TSFr41axEt/dklR 7 | iGRhHdSoyW0Qq4uCUI5VYFs+Yp3ghlQ0ii2koxVBJVv1qA0EXj2Ef2IGE3d8Gbse 8 | nwoGmMkvAgMBAAECggEBAMtFkpUmablKgTnBwjqCvs47OlUVK6AgW8x5qwuwC0Cr 9 | ctXyLcc/vJry/1UPdVZIvDHGv+Cf8Qhw2r7nV49FiqzaBmki9aOR+3uRPB4kvr6L 10 | t8Fw8+5pqlAAJu3wFGqN+M44N2mswDPaAAWpKTu7MGmVY+f+aT03qG1MYOiGoISK 11 | gP6DHiinddD38spM2muyCUyFZk9a+aBEfaQzZoU3gc0yB6R/qBOWZ7NIoIUMicku 12 | Zf3L6/06uunyZp+ueR83j1YWbg3JoYKlGAuQtDRF709+MQrim8lKTnfuHiBeZKYZ 13 | GNLSo7lGjrp6ccSyfXmlA36hSfdlrWtZJ4+utZShftECgYEA+NNOFNa1BLfDw3ot 14 | a6L4W6FE45B32bLbnBdg8foyEYrwzHLPFCbws1Z60pNr7NaCHDIMiKVOXvKQa78d 15 | qdWuPUVJ83uVs9GI8tAo00RAvBn6ut9yaaLa8mIv6ZpfU20IgE5sDjB7IBY9tTVd 16 | EDyJcDuKQXzQ48qmEw86wINQMd0CgYEA1ZMdt7yLnpDiYa6M/BuKjp7PWKcRlzVM 17 | BcCEYHA4LJ6xEOH4y9DEx2y5ljwOcXgJhXAfAyGQr7s1xiP/nXurqfmdP8u7bawp 18 | VwuWJ8Vv0ZXITaU0isezG2Dpnseuion3qSraWlmWUlWLVVgKETZmk7cF7VIXa0NT 19 | LFREdObI5HsCgYBUbm8KRyi5Zxm4VNbgtTYM8ZYMmdLxPe2i85PjyAABT+IRncuC 20 | jQwT7n5Swc9XWBpiMuFp5J3JPgmfZgRMwsMS61YClqbfk3Qi4FtaBMjqiu43Rubt 21 | zWL56DNV0xoRlufRkcq8rdq5spJR0L+5aLFCMhHh0taW1QaxZPOMq4IkyQKBgQC3 22 | GetubGzewqPyzuz77ri5URm+jW0dT4ofnE9hRpRCXMK9EJ52TkOGHYZ2cIKJcTno 23 | dpl/27Tpk/ykJJSu9SnVDbVszkOf4OuIPty6uCAHdPxG5Q3ItTCulkVz5QmUqHf1 24 | RlHxB8FCUSilQFdRLmx+03h3X9vID+4soQoXlwxAJQKBgE5SQpN+TG5V+E4zHgNd 25 | 6cy6gA5dGDJ0KbsgxJwlKTFA9nIcs2ssBxLY9U4x75EGuqpeVNmq6xwwmPtBs0rp 26 | M3W4zdFrZQ3BneFRW7WbSBbsUSprkJW/p4GXa17GzGUq/MDXlGhNlApP1nknzFvE 27 | xGaH0/H/TZxpLCogVP9npUkj 28 | -----END PRIVATE KEY----- --------------------------------------------------------------------------------