desc: "use standard Go tests"
- pkg: github.com/onsi/gomega
desc: "use standard Go tests"
+ http3-internal:
+ list-mode: lax
+ files:
+ - '**/http3/**'
+ deny:
+ - pkg: 'github.com/quic-go/quic-go/internal'
+ desc: 'no dependency on quic-go/internal'
misspell:
ignore-rules:
- ect
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
"github.com/quic-go/qpack"
conn.Context(),
conn,
c.enableDatagrams,
- protocol.PerspectiveClient,
+ false, // client
c.logger,
0,
)
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
"github.com/quic-go/qpack"
var errGoAway = errors.New("connection in graceful shutdown")
+// invalidStreamID is a stream ID that is invalid. The first valid stream ID in QUIC is 0.
+const invalidStreamID = quic.StreamID(-1)
+
// Conn is an HTTP/3 connection.
// It has all methods from the quic.Conn expect for AcceptStream, AcceptUniStream,
// SendDatagram and ReceiveDatagram.
ctx context.Context
- perspective protocol.Perspective
- logger *slog.Logger
+ isServer bool
+ logger *slog.Logger
enableDatagrams bool
decoder *qpack.Decoder
streamMx sync.Mutex
- streams map[protocol.StreamID]*stateTrackingStream
- lastStreamID protocol.StreamID
- maxStreamID protocol.StreamID
+ streams map[quic.StreamID]*stateTrackingStream
+ lastStreamID quic.StreamID
+ maxStreamID quic.StreamID
settings *Settings
receivedSettings chan struct{}
ctx context.Context,
quicConn *quic.Conn,
enableDatagrams bool,
- perspective protocol.Perspective,
+ isServer bool,
logger *slog.Logger,
idleTimeout time.Duration,
) *Conn {
c := &Conn{
ctx: ctx,
conn: quicConn,
- perspective: perspective,
+ isServer: isServer,
logger: logger,
idleTimeout: idleTimeout,
enableDatagrams: enableDatagrams,
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
receivedSettings: make(chan struct{}),
- streams: make(map[protocol.StreamID]*stateTrackingStream),
- maxStreamID: protocol.InvalidStreamID,
- lastStreamID: protocol.InvalidStreamID,
+ streams: make(map[quic.StreamID]*stateTrackingStream),
+ maxStreamID: invalidStreamID,
+ lastStreamID: invalidStreamID,
}
if idleTimeout > 0 {
c.idleTimer = time.AfterFunc(idleTimeout, c.onIdleTimer)
}
// The server is performing a graceful shutdown.
// If no more streams are remaining, close the connection.
- if c.maxStreamID != protocol.InvalidStreamID {
+ if c.maxStreamID != invalidStreamID {
if len(c.streams) == 0 {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
}
c.streamMx.Lock()
maxStreamID := c.maxStreamID
var nextStreamID quic.StreamID
- if c.lastStreamID == protocol.InvalidStreamID {
+ if c.lastStreamID == invalidStreamID {
nextStreamID = 0
} else {
nextStreamID = c.lastStreamID + 4
c.streamMx.Unlock()
// Streams with stream ID equal to or greater than the stream ID carried in the GOAWAY frame
// will be rejected, see section 5.2 of RFC 9114.
- if maxStreamID != protocol.InvalidStreamID && nextStreamID >= maxStreamID {
+ if maxStreamID != invalidStreamID && nextStreamID >= maxStreamID {
return nil, errGoAway
}
// Our QPACK implementation doesn't use the dynamic table yet.
return
case streamTypePushStream:
- switch c.perspective {
- case protocol.PerspectiveClient:
- // we never increased the Push ID, so we don't expect any push streams
- c.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
- case protocol.PerspectiveServer:
+ if c.isServer {
// only the server can push
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "")
+ } else {
+ // we never increased the Push ID, so we don't expect any push streams
+ c.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
}
return
default:
}
// we don't support server push, hence we don't expect any GOAWAY frames from the client
- if c.perspective == protocol.PerspectiveServer {
+ if c.isServer {
return
}
return
}
c.streamMx.Lock()
- if c.maxStreamID != protocol.InvalidStreamID && goaway.StreamID > c.maxStreamID {
+ if c.maxStreamID != invalidStreamID && goaway.StreamID > c.maxStreamID {
c.streamMx.Unlock()
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
return
}
}
-func (c *Conn) sendDatagram(streamID protocol.StreamID, b []byte) error {
+func (c *Conn) sendDatagram(streamID quic.StreamID, b []byte) error {
// TODO: this creates a lot of garbage and an additional copy
data := make([]byte, 0, len(b)+8)
data = quicvarint.Append(data, uint64(streamID/4))
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
return fmt.Errorf("invalid quarter stream id: %w", err)
}
- streamID := protocol.StreamID(4 * quarterStreamID)
+ streamID := quic.StreamID(4 * quarterStreamID)
c.streamMx.Lock()
dg, ok := c.streams[streamID]
c.streamMx.Unlock()
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
"github.com/stretchr/testify/require"
serverConn.Context(),
serverConn,
false,
- protocol.PerspectiveServer,
+ true, // server
nil,
0,
)
context.Background(),
serverConn,
false,
- protocol.PerspectiveServer,
+ true, // server
nil,
0,
)
context.Background(),
serverConn,
false,
- protocol.PerspectiveServer,
+ true, // server
nil,
0,
)
clientConn.Context(),
clientConn,
false,
- protocol.PerspectiveClient,
+ false, // client
nil,
0,
)
clientConn.Context(),
clientConn,
false,
- protocol.PerspectiveClient,
+ false, // client
nil,
0,
)
func TestConnRejectPushStream(t *testing.T) {
t.Run("client", func(t *testing.T) {
- testConnRejectPushStream(t, protocol.PerspectiveClient, ErrCodeStreamCreationError)
+ testConnRejectPushStream(t, false, ErrCodeStreamCreationError)
})
t.Run("server", func(t *testing.T) {
- testConnRejectPushStream(t, protocol.PerspectiveServer, ErrCodeIDError)
+ testConnRejectPushStream(t, true, ErrCodeIDError)
})
}
-func testConnRejectPushStream(t *testing.T, pers protocol.Perspective, expectedErr ErrCode) {
+func testConnRejectPushStream(t *testing.T, isServer bool, expectedErr ErrCode) {
clientConn, serverConn := newConnPair(t)
conn := newConnection(
clientConn.Context(),
clientConn,
false,
- pers.Opposite(),
+ !isServer,
nil,
0,
)
clientConn.Context(),
clientConn,
true,
- protocol.PerspectiveClient,
+ false, // client
nil,
0,
)
clientConn.Context(),
clientConn,
true,
- protocol.PerspectiveClient,
+ false, // client
nil,
0,
)
str, err := conn.openRequestStream(context.Background(), nil, nil, true, 1000)
require.NoError(t, err)
- require.Equal(t, protocol.StreamID(strID), str.StreamID())
+ require.Equal(t, quic.StreamID(strID), str.StreamID())
// now open the stream...
require.NoError(t, serverConn.SendDatagram(append(quarterStreamID, []byte("bar")...)))
clientConn.Context(),
clientConn,
true,
- protocol.PerspectiveClient,
+ false, // client
nil,
0,
)
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/stretchr/testify/require"
)
+// maxByteCount is the maximum value of a ByteCount
+const maxByteCount = uint64(1<<62 - 1)
+
func newUDPConnLocalhost(t testing.TB) *net.UDPConn {
t.Helper()
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
newUDPConnLocalhost(t),
getTLSConfig(),
&quic.Config{
- InitialStreamReceiveWindow: uint64(protocol.MaxByteCount),
- InitialConnectionReceiveWindow: uint64(protocol.MaxByteCount),
+ InitialStreamReceiveWindow: maxByteCount,
+ InitialConnectionReceiveWindow: maxByteCount,
},
)
require.NoError(t, err)
newUDPConnLocalhost(t),
getTLSConfig(),
&quic.Config{
- InitialStreamReceiveWindow: uint64(protocol.MaxByteCount),
- InitialConnectionReceiveWindow: uint64(protocol.MaxByteCount),
+ InitialStreamReceiveWindow: maxByteCount,
+ InitialConnectionReceiveWindow: maxByteCount,
EnableDatagrams: true,
},
)
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIICzDCCAbQCCQDA+rLymNnfJzANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQKDB1x
+dWljLWdvIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0yMDA4MTgwOTIxMzVaFw0z
+MDA4MTYwOTIxMzVaMCgxJjAkBgNVBAoMHXF1aWMtZ28gQ2VydGlmaWNhdGUgQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1OcsYrVaSDfh
+iDppl6oteVspOY3yFb96T9Y/biaGPJAkBO9VGKcqwOUPmUeiWpedRAUB9LE7Srs6
+qBX4mnl90Icjp8jbIs5cPgIWLkIu8Qm549RghFzB3bn+EmCQSe4cxvyDMN3ndClp
+3YMXpZgXWgJGiPOylVi/OwHDdWDBorw4hvry+6yDtpQo2TuI2A/xtxXPT7BgsEJD
+WGffdgZOYXChcFA0c1XVLIYlu2w2JhxS8c2TUF6uSDlmcoONNKVoiNCuu1Z9MorS
+Qmg7a2G7dSPu123KcTcSQFcmJrt+1G81gOBtHB69kacD8xDmgksj09h/ODPL/gIU
+1ZcU2ci1/QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB0Tb1JbLXp/BvWovSAhO/j
+wG7UEaUA1rCtkDB+fV2HS9bxCbV5eErdg8AMHKgB51ygUrq95vm/baZmUILr84XK
+uTEoxxrw5S9Z7SrhtbOpKCumoSeTsCPjDvCcwFExHv4XHFk+CPqZwbMHueVIMT0+
+nGWss/KecCPdJLdnUgMRz0tIuXzkoRuOiUiZfUeyBNVNbDFSrLigYshTeAPGaYjX
+CypoHxkeS93nWfOMUu8FTYLYkvGMU5i076zDoFGKJiEtbjSiNW+Hei7u2aSEuCzp
+qyTKzYPWYffAq3MM2MKJgZdL04e9GEGeuce/qhM1o3q77aI/XJImwEDdut2LDec1
+-----END CERTIFICATE-----
--- /dev/null
+package testdata
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "os"
+ "path"
+ "runtime"
+)
+
+var certPath string
+
+func init() {
+ _, filename, _, ok := runtime.Caller(0)
+ if !ok {
+ panic("Failed to get current frame")
+ }
+
+ certPath = path.Dir(filename)
+}
+
+// GetCertificatePaths returns the paths to certificate and key
+func GetCertificatePaths() (string, string) {
+ return path.Join(certPath, "cert.pem"), path.Join(certPath, "priv.key")
+}
+
+// GetTLSConfig returns a tls config for quic.clemente.io
+func GetTLSConfig() *tls.Config {
+ cert, err := tls.LoadX509KeyPair(GetCertificatePaths())
+ if err != nil {
+ panic(err)
+ }
+ return &tls.Config{
+ MinVersion: tls.VersionTLS13,
+ Certificates: []tls.Certificate{cert},
+ }
+}
+
+// AddRootCA adds the root CA certificate to a cert pool
+func AddRootCA(certPool *x509.CertPool) {
+ caCertPath := path.Join(certPath, "ca.pem")
+ caCertRaw, err := os.ReadFile(caCertPath)
+ if err != nil {
+ panic(err)
+ }
+ if ok := certPool.AppendCertsFromPEM(caCertRaw); !ok {
+ panic("Could not add root ceritificate to pool.")
+ }
+}
+
+// GetRootCA returns an x509.CertPool containing (only) the CA certificate
+func GetRootCA() *x509.CertPool {
+ pool := x509.NewCertPool()
+ AddRootCA(pool)
+ return pool
+}
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIC1TCCAb2gAwIBAgIJAK2fcqC0BVA7MA0GCSqGSIb3DQEBCwUAMCgxJjAkBgNV
+BAoMHXF1aWMtZ28gQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTIwMDgxODA5MjEz
+NVoXDTMwMDgxNjA5MjEzNVowEjEQMA4GA1UECgwHcXVpYy1nbzCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAN/YwrigSXdJCL/bdBGhb0UpqtU8H+krV870
++w1yCSykLImH8x3qHZEXt9sr/vgjcJoV6Z15RZmnbEqnAx84sIClIBoIgnk0VPxu
+WF+/U/dElbftCfYcfJAddhRckdmGB+yb3Wogb32UJ+q3my++h6NjHsYb+OwpJPnQ
+meXjOE7Kkf+bXfFywHF3R8kzVdh5JUFYeKbxYmYgxRps1YTsbCrZCrSy1CbQ9FJw
+Wg5C8t+7yvVFmOeWPECypBCz2xS2mu+kycMNIjIWMl0SL7oVM5cBkRKPeVIG/KcM
+i5+/4lRSLoPh0Txh2TKBWfpzLbIOdPU8/O7cAukIGWx0XsfHUQMCAwEAAaMYMBYw
+FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAyxxvebdMz
+shp5pt1SxMOSXbo8sTa1cpaf2rTmb4nxjXs6KPBEn53hSBz9bhe5wXE4f94SHadf
+636rLh3d75KgrLUwO9Yq0HfCxMo1jUV/Ug++XwcHCI9vk58Tk/H4hqEM6C8RrdTj
+fYeuegQ0/oNLJ4uTw2P2A8TJbL6FC2dcICEAvUGZUcVyZ8m8tHXNRYYh6MZ7ubCh
+hinvL+AA5fY6EVlc5G/P4DN6fYxGn1cFNbiL4uZP4+W3dOmP+NV0YV9ihTyMzz0R
+vSoOZ9FeVkyw8EhMb3LoyXYKazvJy2VQST1ltzAGit9RiM1Gv4vuna74WsFzrn1U
+A/TbaR0ih/qG
+-----END CERTIFICATE-----
--- /dev/null
+package testdata
+
+import (
+ "crypto/tls"
+ "io"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestCertificates(t *testing.T) {
+ ln, err := tls.Listen("tcp", "localhost:4433", GetTLSConfig())
+ require.NoError(t, err)
+
+ go func() {
+ conn, err := ln.Accept()
+ require.NoError(t, err)
+ defer conn.Close()
+ _, err = conn.Write([]byte("foobar"))
+ require.NoError(t, err)
+ }()
+
+ conn, err := tls.Dial("tcp", "localhost:4433", &tls.Config{RootCAs: GetRootCA()})
+ require.NoError(t, err)
+ data, err := io.ReadAll(conn)
+ require.NoError(t, err)
+ require.Equal(t, "foobar", string(data))
+}
--- /dev/null
+#!/bin/bash
+
+set -e
+
+echo "Generating CA key and certificate:"
+openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 \
+ -keyout ca.key -out ca.pem \
+ -subj "/O=quic-go Certificate Authority/"
+
+echo "Generating CSR"
+openssl req -out cert.csr -new -newkey rsa:2048 -nodes -keyout priv.key \
+ -subj "/O=quic-go/"
+
+echo "Sign certificate:"
+openssl x509 -req -sha256 -days 3650 -in cert.csr -out cert.pem \
+ -CA ca.pem -CAkey ca.key -CAcreateserial \
+ -extfile <(printf "subjectAltName=DNS:localhost")
+
+# debug output the certificate
+openssl x509 -noout -text -in cert.pem
+
+# we don't need the CA key, the serial number and the CSR any more
+rm ca.key cert.csr ca.srl
+
--- /dev/null
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDf2MK4oEl3SQi/
+23QRoW9FKarVPB/pK1fO9PsNcgkspCyJh/Md6h2RF7fbK/74I3CaFemdeUWZp2xK
+pwMfOLCApSAaCIJ5NFT8blhfv1P3RJW37Qn2HHyQHXYUXJHZhgfsm91qIG99lCfq
+t5svvoejYx7GG/jsKST50Jnl4zhOypH/m13xcsBxd0fJM1XYeSVBWHim8WJmIMUa
+bNWE7Gwq2Qq0stQm0PRScFoOQvLfu8r1RZjnljxAsqQQs9sUtprvpMnDDSIyFjJd
+Ei+6FTOXAZESj3lSBvynDIufv+JUUi6D4dE8YdkygVn6cy2yDnT1PPzu3ALpCBls
+dF7Hx1EDAgMBAAECggEBAMm+mLDBdbUWk9YmuZNyRdC13wvT5obF05vo26OglXgw
+dxt09b6OVBuCnuff3SpS9pdJDIYq2HnFlSorH/sxopIvQKF17fHDIp1n7ipNTCXd
+IHrmHkY8Il/YzaVIUQMVc2rih0mw9greTqOS20DKnYC6QvAWIeDmrDaitTGl+ge3
+hm7e2lsgZi13R6fTNwQs9geEQSGzP2k7bFceHQFDChOYiQraR5+VZZ8S8AMGjk47
+AUa5EsKeUe6O9t2xuDSFxzYz5eadOAiErKGDos5KXXr3VQgFcC8uPEFFjcJ/yl+8
+tOe4iLeVwGSDJhTAThdR2deJOjaDcarWM7ixmxA3DAECgYEA/WVwmY4gWKwv49IJ
+Jnh1Gu93P772GqliMNpukdjTI+joQxfl4jRSt2hk4b1KRwyT9aaKfvdz0HFlXo/r
+9NVSAYT3/3vbcw61bfvPhhtz44qRAAKua6b5cUM6XqxVt1hqdP8lrf/blvA5ln+u
+O51S8+wpxZMuqKz/29zdWSG6tAMCgYEA4iWXMXX9dZajI6abVkWwuosvOakXdLk4
+tUy7zd+JPF7hmUzzj2gtg4hXoiQPAOi+GY3TX+1Nza3s1LD7iWaXSKeOWvvligw9
+Q/wVTNW2P1+tdhScJf9QudzW69xOm5HNBgx9uWV2cHfjC12vg5aTH0k5axvaq15H
+9WBXlH5q3wECgYBYoYGYBDFmMpvxmMagkSOMz1OrlVSpkLOKmOxx0SBRACc1SIec
+7mY8RqR6nOX9IfYixyTMMittLiyhvb9vfKnZZDQGRcFFZlCpbplws+t+HDqJgWaW
+uumm5zfkY2z7204pLBF24fZhvha2gGRl76pTLTiTJd79Gr3HnmJByd1vFwKBgHL7
+vfYuEeM55lT4Hz8sTAFtR2O/7+cvTgAQteSlZbfGXlp939DonUulhTkxsFc7/3wq
+unCpzcdoSWSTYDGqcf1FBIKKVVltg7EPeR0KBJIQabgCHqrLOBZojPZ7m5RJ+765
+lysuxZvFuTFMPzNe2gssRf+JuBMt6tR+WclsxZYBAoGAEEFs1ppDil1xlP5rdH7T
+d3TSw/u4eU/X8Ei1zi25hdRUiV76fP9fBELYFmSrPBhugYv91vtSv/LmD4zLfLv/
+yzwAD9j1lGbgM8Of8klCkk+XSJ88ryUwnMTJ5loQJW8t4L+zLv5Le7Ca9SAT0kJ1
+jT0GzDymgLMGp8RPdBkpk+w=
+-----END PRIVATE KEY-----
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
"github.com/quic-go/qpack"
connCtx,
conn,
s.EnableDatagrams,
- protocol.PerspectiveServer,
+ true, // server
s.Logger,
s.IdleTimeout,
)
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/testdata"
+ "github.com/quic-go/quic-go/http3/internal/testdata"
"github.com/quic-go/quic-go/quicvarint"
"github.com/stretchr/testify/assert"
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/qpack"
)
s.bytesRemainingInFrame = f.Length
break parseLoop
case *headersFrame:
- if s.conn.perspective == protocol.PerspectiveServer {
+ if s.conn.isServer {
continue
}
if s.parsedTrailer {
return s.datagramStream.Write(b)
}
-func (s *Stream) StreamID() protocol.StreamID {
+func (s *Stream) StreamID() quic.StreamID {
return s.datagramStream.StreamID()
}
}
// StreamID returns the QUIC stream ID of the underlying QUIC stream.
-func (s *RequestStream) StreamID() protocol.StreamID {
+func (s *RequestStream) StreamID() quic.StreamID {
return s.str.StreamID()
}
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/qpack"
clientConn.Context(),
clientConn,
false,
- protocol.PerspectiveClient,
+ false, // client
nil,
0,
),
str := newStream(
qstr,
- newConnection(context.Background(), clientConn, false, protocol.PerspectiveClient, nil, 0),
+ newConnection(context.Background(), clientConn, false, false, nil, 0),
nil,
func(r io.Reader, u uint64) error { return nil },
)
str := newRequestStream(
newStream(
qstr,
- newConnection(context.Background(), clientConn, false, protocol.PerspectiveClient, nil, 0),
+ newConnection(context.Background(), clientConn, false, false, nil, 0),
&httptrace.ClientTrace{},
func(r io.Reader, u uint64) error { return nil },
),
"golang.org/x/net/http/httpguts"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
)
// Settings are HTTP/3 settings that apply to the underlying connection.
}
if len(t.QUICConfig.Versions) == 0 {
t.QUICConfig = t.QUICConfig.Clone()
- t.QUICConfig.Versions = []quic.Version{protocol.SupportedVersions[0]}
+ t.QUICConfig.Versions = []quic.Version{quic.SupportedVersions()[0]}
}
if len(t.QUICConfig.Versions) != 1 {
return errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
"time"
"github.com/quic-go/quic-go"
- "github.com/quic-go/quic-go/internal/protocol"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
func TestTransportMultipleQUICVersions(t *testing.T) {
qconf := &quic.Config{
- Versions: []quic.Version{protocol.Version2, protocol.Version1},
+ Versions: []quic.Version{quic.Version2, quic.Version1},
}
tr := &Transport{QUICConfig: qconf}
req := httptest.NewRequest(http.MethodGet, "https://example.com", nil)
"crypto/tls"
"errors"
"net"
+ "slices"
"time"
"github.com/quic-go/quic-go/internal/handshake"
Version2 = protocol.Version2
)
+// SupportedVersions returns the support versions, sorted in descending order of preference.
+func SupportedVersions() []Version {
+ // clone the slice to prevent the caller from modifying the slice
+ return slices.Clone(protocol.SupportedVersions)
+}
+
// A ClientToken is a token received by the client.
// It can be used to skip address validation on future connection attempts.
type ClientToken struct {