├── .github ├── .gitignore ├── .ci.conf ├── workflows │ ├── api.yaml │ ├── lint.yaml │ ├── reuse.yml │ ├── release.yml │ ├── renovate-go-sum-fix.yaml │ ├── tidy-check.yaml │ ├── codeql-analysis.yml │ ├── fuzz.yaml │ └── test.yaml ├── install-hooks.sh └── fetch-scripts.sh ├── .goreleaser.yml ├── renovate.json ├── streamreadresult.go ├── streamwriteparameters.go ├── transportstopinfo.go ├── .gitignore ├── codecov.yml ├── go.mod ├── .reuse └── dep5 ├── internal └── wrapper │ ├── listener.go │ ├── fakepacketconn.go │ ├── readablestream.go │ ├── writablestream.go │ ├── stream.go │ └── quic.go ├── LICENSES └── MIT.txt ├── LICENSE ├── writablestream.go ├── readablestream.go ├── bidirectionalstream.go ├── transport.go ├── go.sum ├── README.md ├── transportbase.go ├── transport_go_test.go └── .golangci.yml /.github/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Pion community 2 | # SPDX-License-Identifier: MIT 3 | 4 | .goassets 5 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Pion community 2 | # SPDX-License-Identifier: MIT 3 | 4 | builds: 5 | - skip: true 6 | -------------------------------------------------------------------------------- /.github/.ci.conf: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Pion community 2 | # SPDX-License-Identifier: MIT 3 | 4 | GO_MOD_VERSION_EXPECTED=1.24 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>pion/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /streamreadresult.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | // StreamReadResult holds information relating to the result returned from readInto. 7 | type StreamReadResult struct { 8 | Amount int 9 | Finished bool 10 | } 11 | -------------------------------------------------------------------------------- /streamwriteparameters.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | // StreamWriteParameters holds information relating to the data to be written. 7 | type StreamWriteParameters struct { 8 | Data []byte 9 | Finished bool 10 | } 11 | -------------------------------------------------------------------------------- /transportstopinfo.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | // TransportStopInfo holds information relating to the error code for 7 | // stopping a TransportBase. 8 | type TransportStopInfo struct { 9 | ErrorCode uint16 10 | Reason string 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Pion community 2 | # SPDX-License-Identifier: MIT 3 | 4 | ### JetBrains IDE ### 5 | ##################### 6 | .idea/ 7 | 8 | ### Emacs Temporary Files ### 9 | ############################# 10 | *~ 11 | 12 | ### Folders ### 13 | ############### 14 | bin/ 15 | vendor/ 16 | node_modules/ 17 | 18 | ### Files ### 19 | ############# 20 | *.ivf 21 | *.ogg 22 | tags 23 | cover.out 24 | *.sw[poe] 25 | *.wasm 26 | examples/sfu-ws/cert.pem 27 | examples/sfu-ws/key.pem 28 | wasm_exec.js 29 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # 6 | # SPDX-FileCopyrightText: 2023 The Pion community 7 | # SPDX-License-Identifier: MIT 8 | 9 | coverage: 10 | status: 11 | project: 12 | default: 13 | # Allow decreasing 2% of total coverage to avoid noise. 14 | threshold: 2% 15 | patch: 16 | default: 17 | target: 70% 18 | only_pulls: true 19 | 20 | ignore: 21 | - "examples/*" 22 | - "examples/**/*" 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pion/quic 2 | 3 | go 1.24 4 | 5 | toolchain go1.25.5 6 | 7 | require ( 8 | github.com/pion/logging v0.2.4 9 | github.com/pion/transport/v3 v3.1.1 10 | github.com/quic-go/quic-go v0.57.0 11 | github.com/stretchr/testify v1.11.1 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/kr/text v0.2.0 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | golang.org/x/crypto v0.41.0 // indirect 19 | golang.org/x/net v0.43.0 // indirect 20 | golang.org/x/sys v0.35.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: Pion 3 | Source: https://github.com/pion/ 4 | 5 | Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json yarn.lock 6 | Copyright: 2023 The Pion community 7 | License: MIT 8 | 9 | Files: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt 10 | Copyright: 2023 The Pion community 11 | License: CC0-1.0 12 | -------------------------------------------------------------------------------- /.github/workflows/api.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: API 15 | on: 16 | pull_request: 17 | 18 | jobs: 19 | check: 20 | uses: pion/.goassets/.github/workflows/api.reusable.yml@master 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: Lint 15 | on: 16 | pull_request: 17 | 18 | jobs: 19 | lint: 20 | uses: pion/.goassets/.github/workflows/lint.reusable.yml@master 21 | -------------------------------------------------------------------------------- /internal/wrapper/listener.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package wrapper 5 | 6 | import ( 7 | "context" 8 | 9 | quic "github.com/quic-go/quic-go" 10 | ) 11 | 12 | // A Listener for incoming QUIC connections. 13 | type Listener struct { 14 | l *quic.Listener 15 | } 16 | 17 | // Accept accepts incoming streams. 18 | func (l *Listener) Accept() (*Conn, error) { 19 | c, err := l.l.Accept(context.TODO()) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return &Conn{c: c}, nil 25 | } 26 | 27 | // Close closes the listener. 28 | func (l *Listener) Close() error { 29 | return l.l.Close() 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: REUSE Compliance Check 15 | 16 | on: 17 | push: 18 | pull_request: 19 | 20 | jobs: 21 | lint: 22 | uses: pion/.goassets/.github/workflows/reuse.reusable.yml@master 23 | -------------------------------------------------------------------------------- /.github/install-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # DO NOT EDIT THIS FILE 5 | # 6 | # It is automatically copied from https://github.com/pion/.goassets repository. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | SCRIPT_PATH="$(realpath "$(dirname "$0")")" 15 | 16 | . ${SCRIPT_PATH}/fetch-scripts.sh 17 | 18 | cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" 19 | cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" 20 | cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: Release 15 | on: 16 | push: 17 | tags: 18 | - 'v*' 19 | 20 | jobs: 21 | release: 22 | uses: pion/.goassets/.github/workflows/release.reusable.yml@master 23 | with: 24 | go-version: "1.25" # auto-update/latest-go-version 25 | -------------------------------------------------------------------------------- /.github/workflows/renovate-go-sum-fix.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: Fix go.sum 15 | on: 16 | push: 17 | branches: 18 | - renovate/* 19 | 20 | jobs: 21 | fix: 22 | uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master 23 | secrets: 24 | token: ${{ secrets.PIONBOT_PRIVATE_KEY }} 25 | -------------------------------------------------------------------------------- /.github/workflows/tidy-check.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: Go mod tidy 15 | on: 16 | pull_request: 17 | push: 18 | branches: 19 | - master 20 | 21 | jobs: 22 | tidy: 23 | uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master 24 | with: 25 | go-version: "1.25" # auto-update/latest-go-version 26 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: CodeQL 15 | 16 | on: 17 | workflow_dispatch: 18 | schedule: 19 | - cron: '23 5 * * 0' 20 | pull_request: 21 | branches: 22 | - master 23 | paths: 24 | - '**.go' 25 | 26 | jobs: 27 | analyze: 28 | uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master 29 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: Fuzz 15 | on: 16 | push: 17 | branches: 18 | - master 19 | schedule: 20 | - cron: "0 */8 * * *" 21 | 22 | jobs: 23 | fuzz: 24 | uses: pion/.goassets/.github/workflows/fuzz.reusable.yml@master 25 | with: 26 | go-version: "1.25" # auto-update/latest-go-version 27 | fuzz-time: "60s" 28 | -------------------------------------------------------------------------------- /.github/fetch-scripts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # DO NOT EDIT THIS FILE 5 | # 6 | # It is automatically copied from https://github.com/pion/.goassets repository. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | set -eu 15 | 16 | SCRIPT_PATH="$(realpath "$(dirname "$0")")" 17 | GOASSETS_PATH="${SCRIPT_PATH}/.goassets" 18 | 19 | GOASSETS_REF=${GOASSETS_REF:-master} 20 | 21 | if [ -d "${GOASSETS_PATH}" ]; then 22 | if ! git -C "${GOASSETS_PATH}" diff --exit-code; then 23 | echo "${GOASSETS_PATH} has uncommitted changes" >&2 24 | exit 1 25 | fi 26 | git -C "${GOASSETS_PATH}" fetch origin 27 | git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} 28 | git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} 29 | else 30 | git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" 31 | fi 32 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 The Pion community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /writablestream.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/pion/quic/internal/wrapper" 10 | quic "github.com/quic-go/quic-go" 11 | ) 12 | 13 | // WritableStream represents a quic SendStream. 14 | type WritableStream struct { 15 | s *wrapper.WritableStream 16 | } 17 | 18 | // Write writes data to the stream. 19 | func (s *WritableStream) Write(data StreamWriteParameters) error { 20 | _, err := s.s.WriteQuic(data.Data, data.Finished) 21 | 22 | return err 23 | } 24 | 25 | // StreamID returns the ID of the WritableStream. 26 | func (s *WritableStream) StreamID() StreamID { 27 | return StreamID(s.s.StreamID()) 28 | } 29 | 30 | // SetWriteDeadline sets the deadline for future Write calls. A zero value for t means Write will not time out. 31 | func (s *WritableStream) SetWriteDeadline(t time.Time) error { 32 | return s.s.SetWriteDeadline(t) 33 | } 34 | 35 | // Detach detaches the underlying quic-go SendStream. 36 | func (s *WritableStream) Detach() *quic.SendStream { 37 | return s.s.Detach() 38 | } 39 | -------------------------------------------------------------------------------- /internal/wrapper/fakepacketconn.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package wrapper 5 | 6 | import ( 7 | "net" 8 | "time" 9 | ) 10 | 11 | type fakePacketConn struct { 12 | c net.Conn 13 | } 14 | 15 | func newFakePacketConn(conn net.Conn) *fakePacketConn { 16 | return &fakePacketConn{ 17 | c: conn, 18 | } 19 | } 20 | 21 | func (c *fakePacketConn) ReadFrom(p []byte) (int, net.Addr, error) { 22 | n, err := c.c.Read(p) 23 | 24 | return n, c.c.RemoteAddr(), err 25 | } 26 | 27 | func (c *fakePacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { 28 | n, err := c.c.Write(p) 29 | 30 | return n, err 31 | } 32 | 33 | func (c *fakePacketConn) Close() error { 34 | return c.c.Close() 35 | } 36 | 37 | func (c *fakePacketConn) LocalAddr() net.Addr { 38 | return c.c.LocalAddr() 39 | } 40 | 41 | func (c *fakePacketConn) SetDeadline(t time.Time) error { 42 | return c.c.SetDeadline(t) 43 | } 44 | 45 | func (c *fakePacketConn) SetReadDeadline(t time.Time) error { 46 | return c.c.SetReadDeadline(t) 47 | } 48 | 49 | func (c *fakePacketConn) SetWriteDeadline(t time.Time) error { 50 | return c.c.SetWriteDeadline(t) 51 | } 52 | -------------------------------------------------------------------------------- /readablestream.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/pion/quic/internal/wrapper" 10 | quic "github.com/quic-go/quic-go" 11 | ) 12 | 13 | // StreamID is the ID of a quic stream. 14 | type StreamID int64 15 | 16 | // ReadableStream represents a unidirectional quic ReceiveStream. 17 | type ReadableStream struct { 18 | s *wrapper.ReadableStream 19 | } 20 | 21 | // ReadInto reads from the ReadableStream into the buffer. 22 | func (s *ReadableStream) ReadInto(data []byte) (StreamReadResult, error) { 23 | n, fin, err := s.s.ReadQuic(data) 24 | 25 | return StreamReadResult{ 26 | Amount: n, 27 | Finished: fin, 28 | }, err 29 | } 30 | 31 | // StreamID returns the ID of the ReadableStream. 32 | func (s *ReadableStream) StreamID() StreamID { 33 | return StreamID(s.s.StreamID()) 34 | } 35 | 36 | // SetReadDeadline sets the deadline for future Read calls. A zero value for t means Read will not time out. 37 | func (s *ReadableStream) SetReadDeadline(t time.Time) error { 38 | return s.s.SetReadDeadline(t) 39 | } 40 | 41 | // Detach detaches and returns the underlying quic-go ReceiveStream. 42 | func (s *ReadableStream) Detach() *quic.ReceiveStream { 43 | return s.s.Detach() 44 | } 45 | -------------------------------------------------------------------------------- /bidirectionalstream.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/pion/quic/internal/wrapper" 10 | quic "github.com/quic-go/quic-go" 11 | ) 12 | 13 | // BidirectionalStream represents a bidirectional Quic stream. 14 | type BidirectionalStream struct { 15 | s *wrapper.Stream 16 | } 17 | 18 | // Write writes data to the stream. 19 | func (s *BidirectionalStream) Write(data StreamWriteParameters) error { 20 | _, err := s.s.WriteQuic(data.Data, data.Finished) 21 | 22 | return err 23 | } 24 | 25 | // ReadInto reads from the stream into the buffer. 26 | func (s *BidirectionalStream) ReadInto(data []byte) (StreamReadResult, error) { 27 | n, fin, err := s.s.ReadQuic(data) 28 | 29 | return StreamReadResult{ 30 | Amount: n, 31 | Finished: fin, 32 | }, err 33 | } 34 | 35 | // StreamID returns the ID of the QuicStream. 36 | func (s *BidirectionalStream) StreamID() StreamID { 37 | return StreamID(s.s.StreamID()) 38 | } 39 | 40 | // SetDeadline sets read and write deadlines associated with the stream. 41 | // A zero value for t means Read and Write will not timeout. 42 | func (s *BidirectionalStream) SetDeadline(t time.Time) error { 43 | return s.s.SetDeadline(t) 44 | } 45 | 46 | // Detach detaches the underlying quic-go stream. 47 | func (s *BidirectionalStream) Detach() *quic.Stream { 48 | return s.s.Detach() 49 | } 50 | -------------------------------------------------------------------------------- /internal/wrapper/readablestream.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package wrapper 5 | 6 | import ( 7 | "errors" 8 | "io" 9 | "net" 10 | "time" 11 | 12 | quic "github.com/quic-go/quic-go" 13 | ) 14 | 15 | // ReadableStream represents a wrapped quic-go ReceiveStream. 16 | type ReadableStream struct { 17 | s *quic.ReceiveStream 18 | } 19 | 20 | // Read implements the Conn Read method. 21 | func (s *ReadableStream) Read(p []byte) (int, error) { 22 | return s.s.Read(p) 23 | } 24 | 25 | // ReadQuic reads a frame and determines if it is the final frame. 26 | func (s *ReadableStream) ReadQuic(p []byte) (int, bool, error) { 27 | n, err := s.s.Read(p) 28 | fin := false 29 | 30 | if errors.Is(err, io.EOF) { 31 | fin = true 32 | } else if err != nil { 33 | var ne net.Error 34 | if errors.As(err, &ne) { 35 | fin = !ne.Timeout() 36 | } else { 37 | fin = true 38 | } 39 | } 40 | 41 | return n, fin, err 42 | } 43 | 44 | // StreamID returns the ID of the QuicStream. 45 | func (s *ReadableStream) StreamID() int64 { 46 | return int64(s.s.StreamID()) 47 | } 48 | 49 | // SetReadDeadline sets the deadline for future Read calls. A zero value for t means Read will not time out. 50 | func (s *ReadableStream) SetReadDeadline(t time.Time) error { 51 | return s.s.SetReadDeadline(t) 52 | } 53 | 54 | // Detach returns the underlying quic-go ReveiveStream. 55 | func (s *ReadableStream) Detach() *quic.ReceiveStream { 56 | return s.s 57 | } 58 | -------------------------------------------------------------------------------- /internal/wrapper/writablestream.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package wrapper 5 | 6 | import ( 7 | "time" 8 | 9 | quic "github.com/quic-go/quic-go" 10 | ) 11 | 12 | // WritableStream represents a wrapped quic-go SendStream. 13 | type WritableStream struct { 14 | s *quic.SendStream 15 | } 16 | 17 | // Write implements the Conn Write method. 18 | func (s *WritableStream) Write(p []byte, fin bool) (int, error) { 19 | return s.s.Write(p) 20 | } 21 | 22 | // WriteQuic writes a frame and closes the stream if fin is true. 23 | func (s *WritableStream) WriteQuic(p []byte, fin bool) (int, error) { 24 | n, err := s.s.Write(p) 25 | if err != nil { 26 | return n, err 27 | } 28 | if fin { 29 | return n, s.s.Close() 30 | } 31 | 32 | return n, nil 33 | } 34 | 35 | // StreamID returns the ID of the QuicStream. 36 | func (s *WritableStream) StreamID() int64 { 37 | return int64(s.s.StreamID()) 38 | } 39 | 40 | // Close implements the Conn Close method. It is used to close 41 | // the connection. Any calls to Write will be unblocked and return an error. 42 | func (s *WritableStream) Close() error { 43 | return s.s.Close() 44 | } 45 | 46 | // SetWriteDeadline sets the deadline for future Write calls. A zero value for t means Write will not time out. 47 | func (s *WritableStream) SetWriteDeadline(t time.Time) error { 48 | return s.s.SetWriteDeadline(t) 49 | } 50 | 51 | // Detach returns the underlying quic-go SendStream. 52 | func (s *WritableStream) Detach() *quic.SendStream { 53 | return s.s 54 | } 55 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Package quic implements the QUIC API for Client-to-Server Connections 5 | // https://w3c.github.io/webrtc-quic/ 6 | package quic 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "io" 12 | 13 | "github.com/pion/logging" 14 | "github.com/pion/quic/internal/wrapper" 15 | ) 16 | 17 | // Transport is a quic transport focused on client/server use cases. 18 | type Transport struct { 19 | TransportBase 20 | } 21 | 22 | // NewTransport creates a new Transport. 23 | func NewTransport(url string, config *Config) (*Transport, error) { 24 | if config.LoggerFactory == nil { 25 | config.LoggerFactory = logging.NewDefaultLoggerFactory() 26 | } 27 | 28 | cfg := config.clone() 29 | cfg.SkipVerify = true // Using self signed certificates for now 30 | 31 | s, err := wrapper.Dial(context.Background(), url, cfg) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | t := &Transport{} 37 | t.TransportBase.log = config.LoggerFactory.NewLogger("quic") 38 | 39 | return t, t.TransportBase.startBase(s) 40 | } 41 | 42 | // single accept listen for testing. 43 | func newServer(url string, config *Config) (*Transport, io.Closer, error) { 44 | loggerFactory := config.LoggerFactory 45 | if loggerFactory == nil { 46 | loggerFactory = logging.NewDefaultLoggerFactory() 47 | } 48 | 49 | cfg := config.clone() 50 | cfg.SkipVerify = true // Using self signed certificates for now 51 | 52 | list, err := wrapper.Listen(url, cfg) 53 | if err != nil { 54 | return nil, nil, err 55 | } 56 | 57 | s, err := list.Accept() 58 | if err != nil { 59 | if cerr := list.Close(); cerr != nil { 60 | err = fmt.Errorf("failed to close listener (%s) after accept failed: %w", cerr.Error(), err) 61 | } 62 | 63 | return nil, nil, err 64 | } 65 | 66 | t := &Transport{} 67 | t.TransportBase.log = loggerFactory.NewLogger("quic") 68 | 69 | return t, list, t.TransportBase.startBase(s) 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE 3 | # 4 | # It is automatically copied from https://github.com/pion/.goassets repository. 5 | # If this repository should have package specific CI config, 6 | # remove the repository name from .goassets/.github/workflows/assets-sync.yml. 7 | # 8 | # If you want to update the shared CI config, send a PR to 9 | # https://github.com/pion/.goassets instead of this repository. 10 | # 11 | # SPDX-FileCopyrightText: 2023 The Pion community 12 | # SPDX-License-Identifier: MIT 13 | 14 | name: Test 15 | on: 16 | push: 17 | branches: 18 | - master 19 | pull_request: 20 | 21 | jobs: 22 | test: 23 | uses: pion/.goassets/.github/workflows/test.reusable.yml@master 24 | strategy: 25 | matrix: 26 | go: ["1.25", "1.24"] # auto-update/supported-go-version-list 27 | fail-fast: false 28 | with: 29 | go-version: ${{ matrix.go }} 30 | secrets: inherit 31 | 32 | test-i386: 33 | uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master 34 | strategy: 35 | matrix: 36 | go: ["1.25", "1.24"] # auto-update/supported-go-version-list 37 | fail-fast: false 38 | with: 39 | go-version: ${{ matrix.go }} 40 | 41 | test-windows: 42 | uses: pion/.goassets/.github/workflows/test-windows.reusable.yml@master 43 | strategy: 44 | matrix: 45 | go: ["1.25", "1.24"] # auto-update/supported-go-version-list 46 | fail-fast: false 47 | with: 48 | go-version: ${{ matrix.go }} 49 | 50 | test-macos: 51 | uses: pion/.goassets/.github/workflows/test-macos.reusable.yml@master 52 | strategy: 53 | matrix: 54 | go: ["1.25", "1.24"] # auto-update/supported-go-version-list 55 | fail-fast: false 56 | with: 57 | go-version: ${{ matrix.go }} 58 | 59 | test-wasm: 60 | uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master 61 | with: 62 | go-version: "1.25" # auto-update/latest-go-version 63 | secrets: inherit 64 | -------------------------------------------------------------------------------- /internal/wrapper/stream.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package wrapper 5 | 6 | import ( 7 | "errors" 8 | "io" 9 | "net" 10 | "time" 11 | 12 | quic "github.com/quic-go/quic-go" 13 | ) 14 | 15 | // Stream represents a wrapped quic-go Stream. 16 | type Stream struct { 17 | s *quic.Stream 18 | } 19 | 20 | // Read implements the Conn Read method. 21 | func (s *Stream) Read(p []byte) (int, error) { 22 | return s.s.Read(p) 23 | } 24 | 25 | // ReadQuic reads a frame and determines if it is the final frame. 26 | func (s *Stream) ReadQuic(p []byte) (int, bool, error) { 27 | n, err := s.s.Read(p) 28 | fin := false 29 | 30 | if errors.Is(err, io.EOF) { 31 | fin = true 32 | } else if err != nil { 33 | var ne net.Error 34 | if errors.As(err, &ne) { 35 | fin = !ne.Timeout() 36 | } else { 37 | fin = true 38 | } 39 | } 40 | 41 | return n, fin, err 42 | } 43 | 44 | // Write implements the Conn Write method. 45 | func (s *Stream) Write(p []byte, fin bool) (int, error) { 46 | return s.s.Write(p) 47 | } 48 | 49 | // WriteQuic writes a frame and closes the stream if fin is true. 50 | func (s *Stream) WriteQuic(p []byte, fin bool) (int, error) { 51 | n, err := s.s.Write(p) 52 | if err != nil { 53 | return n, err 54 | } 55 | if fin { 56 | return n, s.s.Close() 57 | } 58 | 59 | return n, nil 60 | } 61 | 62 | // StreamID returns the ID of the QuicStream. 63 | func (s *Stream) StreamID() int64 { 64 | return int64(s.s.StreamID()) 65 | } 66 | 67 | // Close implements the Conn Close method. It is used to close 68 | // the connection. Any calls to Read and Write will be unblocked and return an error. 69 | func (s *Stream) Close() error { 70 | return s.s.Close() 71 | } 72 | 73 | // SetDeadline sets read and write deadlines associated with the stream. 74 | // A zero value for t means Read and Write will not timeout. 75 | func (s *Stream) SetDeadline(t time.Time) error { 76 | return s.s.SetDeadline(t) 77 | } 78 | 79 | // Detach returns the underlying quic-go Stream. 80 | func (s *Stream) Detach() *quic.Stream { 81 | return s.s 82 | } 83 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 5 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 6 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 7 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 8 | github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= 9 | github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= 10 | github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= 11 | github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE= 15 | github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= 16 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 17 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 18 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 19 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 20 | go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= 21 | go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= 22 | golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 23 | golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 24 | golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 25 | golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 26 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 27 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 28 | golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 29 | golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 32 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 33 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 34 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Pion WebRTC 3 |
4 | Pion Quic 5 |
6 |

7 |

A pure Go implementation of the WebRTC API

8 |

9 | Pion WebRTC 10 | Sourcegraph Widget 11 | join us on Discord Follow us on Bluesky Twitter Widget 12 | 13 |
14 | GitHub Workflow Status 15 | Go Reference 16 | Coverage Status 17 | Go Report Card 18 | License: MIT 19 |

20 |
21 | 22 | An work in progress experimental QUIC API that implements: 23 | - QUIC API wrapper for Peer-to-peer and server/clients communications. 24 | - [draft-ietf-webtrans-http3-14](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/) 25 | 26 | The library doesn't implement the QUIC protocol itself. It relies on [quic-go](https://github.com/quic-go/quic-go ) for this purpose. 27 | 28 | ----- 29 | 30 | 31 | ### Need Help? 32 | Check out [WebRTC for the Curious](https://webrtcforthecurious.com). A book about WebRTC in depth, not just about the APIs. 33 | Learn the full details of ICE, SCTP, DTLS, SRTP, and how they work together to make up the WebRTC stack. This is also a great 34 | resource if you are trying to debug. Learn the tools of the trade and how to approach WebRTC issues. This book is vendor 35 | agnostic and will not have any Pion specific information. 36 | 37 | Pion has an active community on [Discord](https://discord.gg/PngbdqpFbt). Please ask for help about anything, questions don't have to be Pion specific! 38 | Come share your interesting project you are working on. We are here to support you. 39 | 40 | #### Pure Go 41 | * No Cgo usage 42 | * Wide platform support 43 | * Windows, macOS, Linux, FreeBSD 44 | * iOS, Android 45 | * [WASM](https://github.com/pion/webrtc/wiki/WebAssembly-Development-and-Testing) see [examples](examples/README.md#webassembly) 46 | * 386, amd64, arm, mips, ppc64 47 | * Easy to build *Numbers generated on Intel(R) Core(TM) i5-2520M CPU @ 2.50GHz* 48 | * **Time to build examples/play-from-disk** - 0.66s user 0.20s system 306% cpu 0.279 total 49 | * **Time to run entire test suite** - 25.60s user 9.40s system 45% cpu 1:16.69 total 50 | * Tools to measure performance [provided](https://github.com/pion/rtsp-bench) 51 | 52 | ### Roadmap 53 | The library is in active development, a readmap is coming soon. 54 | 55 | ### Community 56 | Pion has an active community on the [Discord](https://discord.gg/PngbdqpFbt). 57 | 58 | Follow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. 59 | 60 | We are always looking to support **your projects**. Please reach out if you have something to build! 61 | If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) 62 | 63 | ### Contributing 64 | Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible 65 | 66 | ### License 67 | MIT License - see [LICENSE](LICENSE) for full text 68 | -------------------------------------------------------------------------------- /internal/wrapper/quic.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Package wrapper is a wrapper around lucas-clemente/quic-go to match 5 | // the net.Conn based interface used troughout pion/webrtc. 6 | package wrapper 7 | 8 | import ( 9 | "context" 10 | "crypto" 11 | "crypto/tls" 12 | "crypto/x509" 13 | "errors" 14 | "io" 15 | "net" 16 | "strings" 17 | "time" 18 | 19 | "github.com/quic-go/quic-go" 20 | ) 21 | 22 | // Config represents the configuration of a Quic session. 23 | type Config struct { 24 | Certificate *x509.Certificate 25 | PrivateKey crypto.PrivateKey 26 | SkipVerify bool 27 | } 28 | 29 | func getDefaultQuicConfig() *quic.Config { 30 | return &quic.Config{ 31 | MaxIncomingStreams: 1000, 32 | MaxIncomingUniStreams: 1000, 33 | MaxStreamReceiveWindow: 3 << 20, 34 | MaxConnectionReceiveWindow: 9 << 19, 35 | KeepAlivePeriod: 30 * time.Second, 36 | } 37 | } 38 | 39 | var errClientWithoutRemoteAddress = errors.New("quic: creating client without remote address") 40 | 41 | // Client establishes a QUIC session over an existing conn. 42 | func Client(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { 43 | rAddr := conn.RemoteAddr() 44 | if rAddr == nil { 45 | return nil, errClientWithoutRemoteAddress 46 | } 47 | 48 | c, err := quic.Dial(ctx, newFakePacketConn(conn), rAddr, getTLSConfig(config), getDefaultQuicConfig()) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return &Conn{c: c}, nil 54 | } 55 | 56 | // Dial dials the address over quic. 57 | func Dial(ctx context.Context, addr string, config *Config) (*Conn, error) { 58 | c, err := quic.DialAddr(ctx, addr, getTLSConfig(config), getDefaultQuicConfig()) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return &Conn{c: c}, nil 64 | } 65 | 66 | // Server creates a listener for listens for incoming QUIC sessions. 67 | func Server(conn net.Conn, config *Config) (*Listener, error) { 68 | l, err := quic.Listen(newFakePacketConn(conn), getTLSConfig(config), getDefaultQuicConfig()) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return &Listener{l: l}, nil 74 | } 75 | 76 | // Listen listens on the address over quic. 77 | func Listen(addr string, config *Config) (*Listener, error) { 78 | l, err := quic.ListenAddr(addr, getTLSConfig(config), getDefaultQuicConfig()) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return &Listener{l: l}, nil 84 | } 85 | 86 | func getTLSConfig(config *Config) *tls.Config { 87 | /* #nosec G402 */ 88 | return &tls.Config{ 89 | MinVersion: tls.VersionTLS13, 90 | InsecureSkipVerify: config.SkipVerify, 91 | ClientAuth: tls.RequireAnyClientCert, 92 | Certificates: []tls.Certificate{{ 93 | Certificate: [][]byte{config.Certificate.Raw}, 94 | PrivateKey: config.PrivateKey, 95 | }}, 96 | NextProtos: []string{"pion-quic"}, 97 | } 98 | } 99 | 100 | // A Conn is a QUIC connection between two peers. 101 | type Conn struct { 102 | c *quic.Conn 103 | } 104 | 105 | // OpenStream opens a new stream. 106 | func (c *Conn) OpenStream() (*Stream, error) { 107 | str, err := c.c.OpenStream() 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | return &Stream{s: str}, nil 113 | } 114 | 115 | // OpenUniStream opens and returns a new WritableStream. 116 | func (c *Conn) OpenUniStream() (*WritableStream, error) { 117 | str, err := c.c.OpenUniStream() 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return &WritableStream{s: str}, nil 123 | } 124 | 125 | // AcceptStream accepts an incoming stream. 126 | func (c *Conn) AcceptStream() (*Stream, error) { 127 | str, err := c.c.AcceptStream(context.TODO()) 128 | if err != nil { 129 | if strings.HasPrefix(err.Error(), "Application error 0x0") { 130 | //nolint:nilnil // todo fix. 131 | return nil, nil // Errorcode == 0 implies session is closed without error 132 | } 133 | 134 | return nil, err 135 | } 136 | 137 | return &Stream{s: str}, nil 138 | } 139 | 140 | // AcceptUniStream accepts an incoming unidirectional stream and returns a ReadableStream. 141 | func (c *Conn) AcceptUniStream() (*ReadableStream, error) { 142 | str, err := c.c.AcceptUniStream(context.TODO()) 143 | if err != nil { 144 | if strings.HasPrefix(err.Error(), "Application error 0x0") { 145 | //nolint:nilnil // todo fix. 146 | return nil, nil // Errorcode == 0 implies session is closed without error 147 | } 148 | 149 | return nil, err 150 | } 151 | 152 | return &ReadableStream{s: str}, nil 153 | } 154 | 155 | // GetRemoteCertificates returns the certificate chain presented by remote peer. 156 | func (c *Conn) GetRemoteCertificates() []*x509.Certificate { 157 | return c.c.ConnectionState().TLS.PeerCertificates 158 | } 159 | 160 | // Close the connection. 161 | func (c *Conn) Close() error { 162 | return c.c.CloseWithError(0, io.EOF.Error()) 163 | } 164 | 165 | // CloseWithError closes the connection with an error. 166 | // The error must not be nil. 167 | func (c *Conn) CloseWithError(code uint16, err error) error { 168 | e := "nil" 169 | if err != nil { 170 | e = err.Error() 171 | } 172 | 173 | return c.c.CloseWithError(quic.ApplicationErrorCode(code), e) 174 | } 175 | -------------------------------------------------------------------------------- /transportbase.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | package quic 5 | 6 | import ( 7 | "context" 8 | "crypto" 9 | "crypto/x509" 10 | "errors" 11 | "net" 12 | "sync" 13 | 14 | "github.com/pion/logging" 15 | "github.com/pion/quic/internal/wrapper" 16 | ) 17 | 18 | // TransportBase is the base for Transport. Most of the 19 | // functionality of a Transport is in the base class to allow for 20 | // other subclasses (such as a p2p variant) to share the same interface. 21 | type TransportBase struct { 22 | lock sync.RWMutex 23 | onBidirectionalStreamHdlr func(*BidirectionalStream) 24 | onUnidirectionalStreamHdlr func(*ReadableStream) 25 | session *wrapper.Conn 26 | log logging.LeveledLogger 27 | } 28 | 29 | // Config is used to hold the configuration of StartBase. 30 | type Config struct { 31 | Client bool 32 | Certificate *x509.Certificate 33 | PrivateKey crypto.PrivateKey 34 | LoggerFactory logging.LoggerFactory 35 | } 36 | 37 | // StartBase is used to start the TransportBase. Most implementations 38 | // should instead use the methods on quic.Transport or 39 | // webrtc.QUICTransport to setup a Quic connection. 40 | func (b *TransportBase) StartBase(conn net.Conn, config *Config) error { 41 | lf := config.LoggerFactory 42 | if lf == nil { 43 | lf = logging.NewDefaultLoggerFactory() 44 | } 45 | b.log = lf.NewLogger("quic-wrapper") 46 | 47 | cfg := config.clone() 48 | cfg.SkipVerify = true // Using self signed certificates; WebRTC will check the fingerprint 49 | 50 | var con *wrapper.Conn 51 | var err error 52 | if config.Client { 53 | // Assumes the peer offered to be passive and we accepted. 54 | con, err = wrapper.Client(context.Background(), conn, cfg) 55 | } else { 56 | // Assumes we offer to be passive and this is accepted. 57 | var l *wrapper.Listener 58 | l, err = wrapper.Server(conn, cfg) 59 | if err != nil { 60 | return err 61 | } 62 | con, err = l.Accept() 63 | } 64 | 65 | if err != nil { 66 | return err 67 | } 68 | 69 | return b.startBase(con) 70 | } 71 | 72 | func (b *TransportBase) startBase(s *wrapper.Conn) error { 73 | b.session = s 74 | 75 | go b.acceptStreams() 76 | go b.acceptUniStreams() 77 | 78 | return nil 79 | } 80 | 81 | func (c *Config) clone() *wrapper.Config { 82 | return &wrapper.Config{ 83 | Certificate: c.Certificate, 84 | PrivateKey: c.PrivateKey, 85 | } 86 | } 87 | 88 | // CreateBidirectionalStream creates an QuicBidirectionalStream object. 89 | func (b *TransportBase) CreateBidirectionalStream() (*BidirectionalStream, error) { 90 | s, err := b.session.OpenStream() 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return &BidirectionalStream{ 96 | s: s, 97 | }, nil 98 | } 99 | 100 | // CreateUnidirectionalStream creates an QuicWritableStream object. 101 | func (b *TransportBase) CreateUnidirectionalStream() (*WritableStream, error) { 102 | s, err := b.session.OpenUniStream() 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | return &WritableStream{ 108 | s: s, 109 | }, nil 110 | } 111 | 112 | // OnBidirectionalStream allows setting an event handler for that is fired 113 | // when data is received from a BidirectionalStream for the first time. 114 | func (b *TransportBase) OnBidirectionalStream(f func(*BidirectionalStream)) { 115 | b.lock.Lock() 116 | defer b.lock.Unlock() 117 | b.onBidirectionalStreamHdlr = f 118 | } 119 | 120 | // OnUnidirectionalStream allows setting an event handler for that is fired 121 | // when data is received from a UnidirectionalStream for the first time. 122 | func (b *TransportBase) OnUnidirectionalStream(f func(*ReadableStream)) { 123 | b.lock.Lock() 124 | defer b.lock.Unlock() 125 | b.onUnidirectionalStreamHdlr = f 126 | } 127 | 128 | func (b *TransportBase) onBidirectionalStream(s *BidirectionalStream) { 129 | b.lock.Lock() 130 | f := b.onBidirectionalStreamHdlr 131 | b.lock.Unlock() 132 | if f != nil { 133 | go f(s) 134 | } 135 | } 136 | 137 | func (b *TransportBase) onUnidirectionalStream(s *ReadableStream) { 138 | b.lock.Lock() 139 | f := b.onUnidirectionalStreamHdlr 140 | b.lock.Unlock() 141 | if f != nil { 142 | go f(s) 143 | } 144 | } 145 | 146 | // GetRemoteCertificates returns the certificate chain in use by the remote side. 147 | func (b *TransportBase) GetRemoteCertificates() []*x509.Certificate { 148 | return b.session.GetRemoteCertificates() 149 | } 150 | 151 | func (b *TransportBase) acceptStreams() { 152 | for { 153 | stream, err := b.session.AcceptStream() 154 | if err != nil { 155 | b.log.Errorf("Failed to accept stream: %v", err) 156 | stopErr := b.Stop(TransportStopInfo{ 157 | Reason: err.Error(), 158 | }) 159 | if stopErr != nil { 160 | b.log.Errorf("Failed to stop transport: %v", stopErr) 161 | } 162 | 163 | return 164 | } 165 | if stream != nil { 166 | stream := &BidirectionalStream{s: stream} 167 | b.onBidirectionalStream(stream) 168 | } else { 169 | return 170 | } 171 | } 172 | } 173 | 174 | func (b *TransportBase) acceptUniStreams() { 175 | for { 176 | stream, err := b.session.AcceptUniStream() 177 | if err != nil { 178 | b.log.Errorf("Failed to accept stream: %v", err) 179 | stopErr := b.Stop(TransportStopInfo{ 180 | Reason: err.Error(), 181 | }) 182 | if stopErr != nil { 183 | b.log.Errorf("Failed to stop transport: %v", stopErr) 184 | } 185 | 186 | return 187 | } 188 | if stream != nil { 189 | stream := &ReadableStream{s: stream} 190 | b.onUnidirectionalStream(stream) 191 | } else { 192 | return 193 | } 194 | } 195 | } 196 | 197 | // Stop stops and closes the TransportBase. 198 | func (b *TransportBase) Stop(stopInfo TransportStopInfo) error { 199 | b.lock.Lock() 200 | defer b.lock.Unlock() 201 | 202 | if b.session == nil { 203 | return nil 204 | } 205 | 206 | if stopInfo.ErrorCode > 0 || len(stopInfo.Reason) > 0 { 207 | return b.session.CloseWithError(stopInfo.ErrorCode, errors.New(stopInfo.Reason)) //nolint:err113 208 | } 209 | 210 | return b.session.Close() 211 | } 212 | -------------------------------------------------------------------------------- /transport_go_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Pion community 2 | // SPDX-License-Identifier: MIT 3 | 4 | //go:build !js 5 | // +build !js 6 | 7 | package quic 8 | 9 | import ( 10 | "bytes" 11 | "crypto" 12 | "crypto/ecdsa" 13 | "crypto/elliptic" 14 | "crypto/rand" 15 | "crypto/x509" 16 | "crypto/x509/pkix" 17 | "encoding/binary" 18 | "encoding/hex" 19 | "fmt" 20 | "io" 21 | "math/big" 22 | "sync" 23 | "testing" 24 | "time" 25 | 26 | "github.com/pion/transport/v3/test" 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestTransport_E2E(t *testing.T) { 31 | // Limit runtime in case of deadlocks 32 | lim := test.TimeOut(time.Second * 20) 33 | defer lim.Stop() 34 | 35 | report := test.CheckRoutines(t) 36 | defer report() 37 | 38 | url := "localhost:50000" 39 | 40 | cert, key, err := GenerateSelfSigned() 41 | assert.NoError(t, err) 42 | 43 | cfgA := &Config{Certificate: cert, PrivateKey: key} 44 | 45 | cert, key, err = GenerateSelfSigned() 46 | assert.NoError(t, err) 47 | 48 | cfgB := &Config{Certificate: cert, PrivateKey: key} 49 | 50 | srvErr := make(chan error) 51 | 52 | var tb *Transport 53 | var lisClose io.Closer 54 | 55 | var ( 56 | clientTx bytes.Buffer // control buffer for comparison 57 | 58 | clientBidiRx bytes.Buffer // receive buffer of bidirectional stream for client 59 | serverBidiRx bytes.Buffer // receive buffer of bidirectional stream for server 60 | 61 | serverUnidiRx bytes.Buffer // receive buffer of unidirectional stream for server 62 | 63 | clientDone sync.WaitGroup 64 | serverDone sync.WaitGroup 65 | ) 66 | 67 | go func() { // server accept and read spawn 68 | defer close(srvErr) 69 | 70 | var sErr error 71 | tb, lisClose, sErr = newServer(url, cfgB) 72 | if sErr != nil { 73 | t.Log("newServer err:", err) 74 | srvErr <- sErr 75 | 76 | return 77 | } 78 | 79 | tb.OnBidirectionalStream(func(stream *BidirectionalStream) { 80 | serverDone.Add(1) 81 | go readBidiLoop(t, stream, &serverBidiRx, &serverDone) // Read to pull incoming messages 82 | }) 83 | tb.OnUnidirectionalStream(func(stream *ReadableStream) { 84 | serverDone.Add(1) 85 | go readUnidiLoop(t, stream, &serverUnidiRx, &serverDone) 86 | }) 87 | }() 88 | 89 | // client dial and send/write 90 | ta, err := NewTransport(url, cfgA) 91 | assert.NoError(t, err) 92 | 93 | err = <-srvErr 94 | assert.NoError(t, err) 95 | 96 | stream, err := ta.CreateBidirectionalStream() 97 | assert.NoError(t, err) 98 | 99 | writablestream, err := ta.CreateUnidirectionalStream() 100 | assert.NoError(t, err) 101 | 102 | // Read to pull incoming messages, should stay empty 103 | clientDone.Add(1) 104 | go readBidiLoop(t, stream, &clientBidiRx, &clientDone) 105 | 106 | count := 512 // how many patterns to send 107 | repeat := 128 // how often to repeat the testData pattern 108 | 109 | // sent side 110 | var buf [2]byte 111 | for i := 0; i < count; i++ { 112 | testData := bytes.Repeat([]byte(fmt.Sprintf("%04d", i)), repeat) 113 | binary.BigEndian.PutUint16(buf[:], uint16(i)) //nolint:gosec 114 | testData = append(testData, buf[0], buf[1]) 115 | 116 | _, _ = clientTx.Write(testData) // writing to a buffer never fails (hi golint) 117 | 118 | data := StreamWriteParameters{Data: testData} 119 | if i == count-1 { 120 | data.Finished = true 121 | } 122 | err = stream.Write(data) 123 | assert.NoError(t, err) 124 | 125 | err = writablestream.Write(data) 126 | assert.NoError(t, err) 127 | } 128 | 129 | serverDone.Wait() 130 | 131 | wantBytes := count * (4*repeat + 2) 132 | assert.Equal(t, wantBytes, clientTx.Len()) 133 | assert.Equal(t, wantBytes, serverBidiRx.Len()) 134 | assert.Equal(t, wantBytes, serverUnidiRx.Len()) 135 | assert.Equal(t, clientTx.Bytes(), serverBidiRx.Bytes()) 136 | assert.Equal(t, clientTx.Bytes(), serverUnidiRx.Bytes()) 137 | 138 | assert.Equal(t, 0, clientBidiRx.Len()) 139 | 140 | err = ta.Stop(TransportStopInfo{}) 141 | assert.NoError(t, err) 142 | 143 | err = tb.Stop(TransportStopInfo{}) 144 | assert.NoError(t, err) 145 | 146 | clientDone.Wait() 147 | assert.NoError(t, lisClose.Close()) 148 | } 149 | 150 | func readBidiLoop(t *testing.T, s *BidirectionalStream, buf io.Writer, done *sync.WaitGroup) { 151 | t.Helper() 152 | defer done.Done() 153 | bufSz := 1024 154 | buffer := make([]byte, bufSz) 155 | for { 156 | res, err := s.ReadInto(buffer) 157 | _, werr := buf.Write(buffer[:res.Amount]) 158 | assert.NoError(t, werr, "buffer.Write never failes(?)") 159 | if err != nil || res.Finished { 160 | return 161 | } 162 | buffer = buffer[:bufSz] 163 | } 164 | } 165 | 166 | func readUnidiLoop(t *testing.T, s *ReadableStream, buf io.Writer, done *sync.WaitGroup) { 167 | t.Helper() 168 | defer done.Done() 169 | bufSz := 1024 170 | buffer := make([]byte, bufSz) 171 | for { 172 | res, err := s.ReadInto(buffer) 173 | _, werr := buf.Write(buffer[:res.Amount]) 174 | assert.NoError(t, werr, "buffer.Write never failes(?)") 175 | if err != nil || res.Finished { 176 | return 177 | } 178 | buffer = buffer[:bufSz] 179 | } 180 | } 181 | 182 | // GenerateSelfSigned creates a self-signed certificate. 183 | func GenerateSelfSigned() (*x509.Certificate, crypto.PrivateKey, error) { 184 | priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 185 | if err != nil { 186 | return nil, nil, err 187 | } 188 | 189 | origin := make([]byte, 16) 190 | 191 | // Max random value, a 130-bits integer, i.e 2^130 - 1 192 | maxBigInt := new(big.Int) 193 | /* #nosec */ 194 | maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1)) 195 | serialNumber, err := rand.Int(rand.Reader, maxBigInt) 196 | if err != nil { 197 | return nil, nil, err 198 | } 199 | 200 | template := x509.Certificate{ 201 | ExtKeyUsage: []x509.ExtKeyUsage{ 202 | x509.ExtKeyUsageClientAuth, 203 | x509.ExtKeyUsageServerAuth, 204 | }, 205 | BasicConstraintsValid: true, 206 | NotBefore: time.Now(), 207 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 208 | NotAfter: time.Now().AddDate(0, 1, 0), 209 | SerialNumber: serialNumber, 210 | Version: 2, 211 | Subject: pkix.Name{CommonName: hex.EncodeToString(origin)}, 212 | IsCA: true, 213 | } 214 | 215 | raw, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 216 | if err != nil { 217 | return nil, nil, err 218 | } 219 | 220 | cert, err := x509.ParseCertificate(raw) 221 | if err != nil { 222 | return nil, nil, err 223 | } 224 | 225 | return cert, priv, nil 226 | } 227 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Pion community 2 | # SPDX-License-Identifier: MIT 3 | 4 | version: "2" 5 | linters: 6 | enable: 7 | - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers 8 | - bidichk # Checks for dangerous unicode character sequences 9 | - bodyclose # checks whether HTTP response body is closed successfully 10 | - containedctx # containedctx is a linter that detects struct contained context.Context field 11 | - contextcheck # check the function whether use a non-inherited context 12 | - cyclop # checks function and package cyclomatic complexity 13 | - decorder # check declaration order and count of types, constants, variables and functions 14 | - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) 15 | - dupl # Tool for code clone detection 16 | - durationcheck # check for two durations multiplied together 17 | - err113 # Golang linter to check the errors handling expressions 18 | - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases 19 | - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. 20 | - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. 21 | - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. 22 | - exhaustive # check exhaustiveness of enum switch statements 23 | - forbidigo # Forbids identifiers 24 | - forcetypeassert # finds forced type assertions 25 | - gochecknoglobals # Checks that no globals are present in Go code 26 | - gocognit # Computes and checks the cognitive complexity of functions 27 | - goconst # Finds repeated strings that could be replaced by a constant 28 | - gocritic # The most opinionated Go source code linter 29 | - gocyclo # Computes and checks the cyclomatic complexity of functions 30 | - godot # Check if comments end in a period 31 | - godox # Tool for detection of FIXME, TODO and other comment keywords 32 | - goheader # Checks is file header matches to pattern 33 | - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. 34 | - goprintffuncname # Checks that printf-like functions are named with `f` at the end 35 | - gosec # Inspects source code for security problems 36 | - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string 37 | - grouper # An analyzer to analyze expression groups. 38 | - importas # Enforces consistent import aliases 39 | - ineffassign # Detects when assignments to existing variables are not used 40 | - lll # Reports long lines 41 | - maintidx # maintidx measures the maintainability index of each function. 42 | - makezero # Finds slice declarations with non-zero initial length 43 | - misspell # Finds commonly misspelled English words in comments 44 | - nakedret # Finds naked returns in functions greater than a specified function length 45 | - nestif # Reports deeply nested if statements 46 | - nilerr # Finds the code that returns nil even if it checks that the error is not nil. 47 | - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. 48 | - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity 49 | - noctx # noctx finds sending http request without context.Context 50 | - predeclared # find code that shadows one of Go's predeclared identifiers 51 | - revive # golint replacement, finds style mistakes 52 | - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks 53 | - tagliatelle # Checks the struct tags. 54 | - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers 55 | - unconvert # Remove unnecessary type conversions 56 | - unparam # Reports unused function parameters 57 | - unused # Checks Go code for unused constants, variables, functions and types 58 | - varnamelen # checks that the length of a variable's name matches its scope 59 | - wastedassign # wastedassign finds wasted assignment statements 60 | - whitespace # Tool for detection of leading and trailing whitespace 61 | disable: 62 | - depguard # Go linter that checks if package imports are in a list of acceptable packages 63 | - funlen # Tool for detection of long functions 64 | - gochecknoinits # Checks that no init functions are present in Go code 65 | - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. 66 | - interfacebloat # A linter that checks length of interface. 67 | - ireturn # Accept Interfaces, Return Concrete Types 68 | - mnd # An analyzer to detect magic numbers 69 | - nolintlint # Reports ill-formed or insufficient nolint directives 70 | - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test 71 | - prealloc # Finds slice declarations that could potentially be preallocated 72 | - promlinter # Check Prometheus metrics naming via promlint 73 | - rowserrcheck # checks whether Err of rows is checked successfully 74 | - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. 75 | - testpackage # linter that makes you use a separate _test package 76 | - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes 77 | - wrapcheck # Checks that errors returned from external packages are wrapped 78 | - wsl # Whitespace Linter - Forces you to use empty lines! 79 | settings: 80 | staticcheck: 81 | checks: 82 | - all 83 | - -QF1008 # "could remove embedded field", to keep it explicit! 84 | - -QF1003 # "could use tagged switch on enum", Cases conflicts with exhaustive! 85 | exhaustive: 86 | default-signifies-exhaustive: true 87 | forbidigo: 88 | forbid: 89 | - pattern: ^fmt.Print(f|ln)?$ 90 | - pattern: ^log.(Panic|Fatal|Print)(f|ln)?$ 91 | - pattern: ^os.Exit$ 92 | - pattern: ^panic$ 93 | - pattern: ^print(ln)?$ 94 | - pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$ 95 | pkg: ^testing$ 96 | msg: use testify/assert instead 97 | analyze-types: true 98 | gomodguard: 99 | blocked: 100 | modules: 101 | - github.com/pkg/errors: 102 | recommendations: 103 | - errors 104 | govet: 105 | enable: 106 | - shadow 107 | revive: 108 | rules: 109 | # Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility 110 | - name: use-any 111 | severity: warning 112 | disabled: false 113 | misspell: 114 | locale: US 115 | varnamelen: 116 | max-distance: 12 117 | min-name-length: 2 118 | ignore-type-assert-ok: true 119 | ignore-map-index-ok: true 120 | ignore-chan-recv-ok: true 121 | ignore-decls: 122 | - i int 123 | - n int 124 | - w io.Writer 125 | - r io.Reader 126 | - b []byte 127 | exclusions: 128 | generated: lax 129 | rules: 130 | - linters: 131 | - forbidigo 132 | - gocognit 133 | path: (examples|main\.go) 134 | - linters: 135 | - gocognit 136 | path: _test\.go 137 | - linters: 138 | - forbidigo 139 | path: cmd 140 | formatters: 141 | enable: 142 | - gci # Gci control golang package import order and make it always deterministic. 143 | - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification 144 | - gofumpt # Gofumpt checks whether code was gofumpt-ed. 145 | - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports 146 | exclusions: 147 | generated: lax 148 | --------------------------------------------------------------------------------