mirror of
https://github.com/golang/net.git
synced 2026-03-31 10:27:08 +09:00
quic: use testing/synctest
Replace bespoke fake time and synchronization with testing/synctest. Change-Id: Ic3fe9635dbad36c890783c38e00708c6cb7a15f8 Reviewed-on: https://go-review.googlesource.com/c/net/+/714482 Reviewed-by: Nicholas Husin <nsh@golang.org> Reviewed-by: Nicholas Husin <husin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
committed by
Gopher Robot
parent
fff0469cf5
commit
d1f64cc670
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestConfigTransportParameters(t *testing.T) {
|
||||
synctest.Test(t, testConfigTransportParameters)
|
||||
}
|
||||
func testConfigTransportParameters(t *testing.T) {
|
||||
const (
|
||||
wantInitialMaxData = int64(1)
|
||||
wantInitialMaxStreamData = int64(2)
|
||||
|
||||
71
quic/conn.go
71
quic/conn.go
@@ -69,23 +69,12 @@ type connTestHooks interface {
|
||||
// init is called after a conn is created.
|
||||
init(first bool)
|
||||
|
||||
// nextMessage is called to request the next event from msgc.
|
||||
// Used to give tests control of the connection event loop.
|
||||
nextMessage(msgc chan any, nextTimeout time.Time) (now time.Time, message any)
|
||||
|
||||
// handleTLSEvent is called with each TLS event.
|
||||
handleTLSEvent(tls.QUICEvent)
|
||||
|
||||
// newConnID is called to generate a new connection ID.
|
||||
// Permits tests to generate consistent connection IDs rather than random ones.
|
||||
newConnID(seq int64) ([]byte, error)
|
||||
|
||||
// waitUntil blocks until the until func returns true or the context is done.
|
||||
// Used to synchronize asynchronous blocking operations in tests.
|
||||
waitUntil(ctx context.Context, until func() bool) error
|
||||
|
||||
// timeNow returns the current time.
|
||||
timeNow() time.Time
|
||||
}
|
||||
|
||||
// newServerConnIDs is connection IDs associated with a new server connection.
|
||||
@@ -102,7 +91,6 @@ func newConn(now time.Time, side connSide, cids newServerConnIDs, peerHostname s
|
||||
endpoint: e,
|
||||
config: config,
|
||||
peerAddr: unmapAddrPort(peerAddr),
|
||||
msgc: make(chan any, 1),
|
||||
donec: make(chan struct{}),
|
||||
peerAckDelayExponent: -1,
|
||||
}
|
||||
@@ -299,17 +287,12 @@ func (c *Conn) loop(now time.Time) {
|
||||
// The connection timer sends a message to the connection loop on expiry.
|
||||
// We need to give it an expiry when creating it, so set the initial timeout to
|
||||
// an arbitrary large value. The timer will be reset before this expires (and it
|
||||
// isn't a problem if it does anyway). Skip creating the timer in tests which
|
||||
// take control of the connection message loop.
|
||||
var timer *time.Timer
|
||||
// isn't a problem if it does anyway).
|
||||
var lastTimeout time.Time
|
||||
hooks := c.testHooks
|
||||
if hooks == nil {
|
||||
timer = time.AfterFunc(1*time.Hour, func() {
|
||||
c.sendMsg(timerEvent{})
|
||||
})
|
||||
defer timer.Stop()
|
||||
}
|
||||
timer := time.AfterFunc(1*time.Hour, func() {
|
||||
c.sendMsg(timerEvent{})
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
for c.lifetime.state != connStateDone {
|
||||
sendTimeout := c.maybeSend(now) // try sending
|
||||
@@ -326,10 +309,7 @@ func (c *Conn) loop(now time.Time) {
|
||||
}
|
||||
|
||||
var m any
|
||||
if hooks != nil {
|
||||
// Tests only: Wait for the test to tell us to continue.
|
||||
now, m = hooks.nextMessage(c.msgc, nextTimeout)
|
||||
} else if !nextTimeout.IsZero() && nextTimeout.Before(now) {
|
||||
if !nextTimeout.IsZero() && nextTimeout.Before(now) {
|
||||
// A connection timer has expired.
|
||||
now = time.Now()
|
||||
m = timerEvent{}
|
||||
@@ -372,6 +352,9 @@ func (c *Conn) loop(now time.Time) {
|
||||
case func(time.Time, *Conn):
|
||||
// Send a func to msgc to run it on the main Conn goroutine
|
||||
m(now, c)
|
||||
case func(now, next time.Time, _ *Conn):
|
||||
// Send a func to msgc to run it on the main Conn goroutine
|
||||
m(now, nextTimeout, c)
|
||||
default:
|
||||
panic(fmt.Sprintf("quic: unrecognized conn message %T", m))
|
||||
}
|
||||
@@ -410,31 +393,7 @@ func (c *Conn) runOnLoop(ctx context.Context, f func(now time.Time, c *Conn)) er
|
||||
defer close(donec)
|
||||
f(now, c)
|
||||
}
|
||||
if c.testHooks != nil {
|
||||
// In tests, we can't rely on being able to send a message immediately:
|
||||
// c.msgc might be full, and testConnHooks.nextMessage might be waiting
|
||||
// for us to block before it processes the next message.
|
||||
// To avoid a deadlock, we send the message in waitUntil.
|
||||
// If msgc is empty, the message is buffered.
|
||||
// If msgc is full, we block and let nextMessage process the queue.
|
||||
msgc := c.msgc
|
||||
c.testHooks.waitUntil(ctx, func() bool {
|
||||
for {
|
||||
select {
|
||||
case msgc <- msg:
|
||||
msgc = nil // send msg only once
|
||||
case <-donec:
|
||||
return true
|
||||
case <-c.donec:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
c.sendMsg(msg)
|
||||
}
|
||||
c.sendMsg(msg)
|
||||
select {
|
||||
case <-donec:
|
||||
case <-c.donec:
|
||||
@@ -444,16 +403,6 @@ func (c *Conn) runOnLoop(ctx context.Context, f func(now time.Time, c *Conn)) er
|
||||
}
|
||||
|
||||
func (c *Conn) waitOnDone(ctx context.Context, ch <-chan struct{}) error {
|
||||
if c.testHooks != nil {
|
||||
return c.testHooks.waitUntil(ctx, func() bool {
|
||||
select {
|
||||
case <-ch:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
// Check the channel before the context.
|
||||
// We always prefer to return results when available,
|
||||
// even when provided with an already-canceled context.
|
||||
|
||||
@@ -2,44 +2,21 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
// asyncTestState permits handling asynchronous operations in a synchronous test.
|
||||
//
|
||||
// For example, a test may want to write to a stream and observe that
|
||||
// STREAM frames are sent with the contents of the write in response
|
||||
// to MAX_STREAM_DATA frames received from the peer.
|
||||
// The Stream.Write is an asynchronous operation, but the test is simpler
|
||||
// if we can start the write, observe the first STREAM frame sent,
|
||||
// send a MAX_STREAM_DATA frame, observe the next STREAM frame sent, etc.
|
||||
//
|
||||
// We do this by instrumenting points where operations can block.
|
||||
// We start async operations like Write in a goroutine,
|
||||
// and wait for the operation to either finish or hit a blocking point.
|
||||
// When the connection event loop is idle, we check a list of
|
||||
// blocked operations to see if any can be woken.
|
||||
type asyncTestState struct {
|
||||
mu sync.Mutex
|
||||
notify chan struct{}
|
||||
blocked map[*blockedAsync]struct{}
|
||||
}
|
||||
|
||||
// An asyncOp is an asynchronous operation that results in (T, error).
|
||||
type asyncOp[T any] struct {
|
||||
v T
|
||||
err error
|
||||
|
||||
caller string
|
||||
tc *testConn
|
||||
v T
|
||||
err error
|
||||
donec chan struct{}
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
@@ -47,17 +24,18 @@ type asyncOp[T any] struct {
|
||||
// cancel cancels the async operation's context, and waits for
|
||||
// the operation to complete.
|
||||
func (a *asyncOp[T]) cancel() {
|
||||
synctest.Wait()
|
||||
select {
|
||||
case <-a.donec:
|
||||
return // already done
|
||||
default:
|
||||
}
|
||||
a.cancelFunc()
|
||||
<-a.tc.asyncTestState.notify
|
||||
synctest.Wait()
|
||||
select {
|
||||
case <-a.donec:
|
||||
default:
|
||||
panic(fmt.Errorf("%v: async op failed to finish after being canceled", a.caller))
|
||||
panic(fmt.Errorf("async op failed to finish after being canceled"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,115 +49,30 @@ var errNotDone = errors.New("async op is not done")
|
||||
// control over the progress of operations, an asyncOp can only
|
||||
// become done in reaction to the test taking some action.
|
||||
func (a *asyncOp[T]) result() (v T, err error) {
|
||||
a.tc.wait()
|
||||
synctest.Wait()
|
||||
select {
|
||||
case <-a.donec:
|
||||
return a.v, a.err
|
||||
default:
|
||||
return v, errNotDone
|
||||
return a.v, errNotDone
|
||||
}
|
||||
}
|
||||
|
||||
// A blockedAsync is a blocked async operation.
|
||||
type blockedAsync struct {
|
||||
until func() bool // when this returns true, the operation is unblocked
|
||||
donec chan struct{} // closed when the operation is unblocked
|
||||
}
|
||||
|
||||
type asyncContextKey struct{}
|
||||
|
||||
// runAsync starts an asynchronous operation.
|
||||
//
|
||||
// The function f should call a blocking function such as
|
||||
// Stream.Write or Conn.AcceptStream and return its result.
|
||||
// It must use the provided context.
|
||||
func runAsync[T any](tc *testConn, f func(context.Context) (T, error)) *asyncOp[T] {
|
||||
as := &tc.asyncTestState
|
||||
if as.notify == nil {
|
||||
as.notify = make(chan struct{})
|
||||
as.mu.Lock()
|
||||
as.blocked = make(map[*blockedAsync]struct{})
|
||||
as.mu.Unlock()
|
||||
}
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
ctx := context.WithValue(context.Background(), asyncContextKey{}, true)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ctx, cancel := context.WithCancel(tc.t.Context())
|
||||
a := &asyncOp[T]{
|
||||
tc: tc,
|
||||
caller: fmt.Sprintf("%v:%v", filepath.Base(file), line),
|
||||
donec: make(chan struct{}),
|
||||
cancelFunc: cancel,
|
||||
}
|
||||
go func() {
|
||||
defer close(a.donec)
|
||||
a.v, a.err = f(ctx)
|
||||
close(a.donec)
|
||||
as.notify <- struct{}{}
|
||||
}()
|
||||
tc.t.Cleanup(func() {
|
||||
if _, err := a.result(); err == errNotDone {
|
||||
tc.t.Errorf("%v: async operation is still executing at end of test", a.caller)
|
||||
a.cancel()
|
||||
}
|
||||
})
|
||||
// Wait for the operation to either finish or block.
|
||||
<-as.notify
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
return a
|
||||
}
|
||||
|
||||
// waitUntil waits for a blocked async operation to complete.
|
||||
// The operation is complete when the until func returns true.
|
||||
func (as *asyncTestState) waitUntil(ctx context.Context, until func() bool) error {
|
||||
if until() {
|
||||
return nil
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
// Context has already expired.
|
||||
return err
|
||||
}
|
||||
if ctx.Value(asyncContextKey{}) == nil {
|
||||
// Context is not one that we've created, and hasn't expired.
|
||||
// This probably indicates that we've tried to perform a
|
||||
// blocking operation without using the async test harness here,
|
||||
// which may have unpredictable results.
|
||||
panic("blocking async point with unexpected Context")
|
||||
}
|
||||
b := &blockedAsync{
|
||||
until: until,
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
// Record this as a pending blocking operation.
|
||||
as.mu.Lock()
|
||||
as.blocked[b] = struct{}{}
|
||||
as.mu.Unlock()
|
||||
// Notify the creator of the operation that we're blocked,
|
||||
// and wait to be woken up.
|
||||
as.notify <- struct{}{}
|
||||
select {
|
||||
case <-b.donec:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wakeAsync tries to wake up a blocked async operation.
|
||||
// It returns true if one was woken, false otherwise.
|
||||
func (as *asyncTestState) wakeAsync() bool {
|
||||
as.mu.Lock()
|
||||
var woken *blockedAsync
|
||||
for w := range as.blocked {
|
||||
if w.until() {
|
||||
woken = w
|
||||
delete(as.blocked, w)
|
||||
break
|
||||
}
|
||||
}
|
||||
as.mu.Unlock()
|
||||
if woken == nil {
|
||||
return false
|
||||
}
|
||||
close(woken.donec)
|
||||
<-as.notify // must not hold as.mu while blocked here
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -9,10 +11,14 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConnCloseResponseBackoff(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseResponseBackoff)
|
||||
}
|
||||
func testConnCloseResponseBackoff(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide, func(c *Config) {
|
||||
clear(c.StatelessResetKey[:])
|
||||
})
|
||||
@@ -34,18 +40,18 @@ func TestConnCloseResponseBackoff(t *testing.T) {
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
tc.wantIdle("packets received immediately after CONN_CLOSE receive no response")
|
||||
|
||||
tc.advance(1100 * time.Microsecond)
|
||||
time.Sleep(1100 * time.Microsecond)
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
tc.wantFrame("receiving packet 1.1ms after CONN_CLOSE generates another CONN_CLOSE",
|
||||
packetType1RTT, debugFrameConnectionCloseTransport{
|
||||
code: errNo,
|
||||
})
|
||||
|
||||
tc.advance(1100 * time.Microsecond)
|
||||
time.Sleep(1100 * time.Microsecond)
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
tc.wantIdle("no response to packet, because CONN_CLOSE backoff is now 2ms")
|
||||
|
||||
tc.advance(1000 * time.Microsecond)
|
||||
time.Sleep(1000 * time.Microsecond)
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
tc.wantFrame("2ms since last CONN_CLOSE, receiving a packet generates another CONN_CLOSE",
|
||||
packetType1RTT, debugFrameConnectionCloseTransport{
|
||||
@@ -55,7 +61,7 @@ func TestConnCloseResponseBackoff(t *testing.T) {
|
||||
t.Errorf("conn.Wait() = %v, want still waiting", err)
|
||||
}
|
||||
|
||||
tc.advance(100000 * time.Microsecond)
|
||||
time.Sleep(100000 * time.Microsecond)
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
tc.wantIdle("drain timer expired, no more responses")
|
||||
|
||||
@@ -68,6 +74,9 @@ func TestConnCloseResponseBackoff(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseWithPeerResponse(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseWithPeerResponse)
|
||||
}
|
||||
func testConnCloseWithPeerResponse(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc := newTestConn(t, clientSide, qr.config)
|
||||
tc.handshake()
|
||||
@@ -99,7 +108,7 @@ func TestConnCloseWithPeerResponse(t *testing.T) {
|
||||
t.Errorf("non-blocking conn.Wait() = %v, want %v", err, wantErr)
|
||||
}
|
||||
|
||||
tc.advance(1 * time.Second) // long enough to exit the draining state
|
||||
time.Sleep(1 * time.Second) // long enough to exit the draining state
|
||||
qr.wantEvents(t, jsonEvent{
|
||||
"name": "connectivity:connection_closed",
|
||||
"data": map[string]any{
|
||||
@@ -109,6 +118,9 @@ func TestConnCloseWithPeerResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnClosePeerCloses(t *testing.T) {
|
||||
synctest.Test(t, testConnClosePeerCloses)
|
||||
}
|
||||
func testConnClosePeerCloses(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc := newTestConn(t, clientSide, qr.config)
|
||||
tc.handshake()
|
||||
@@ -137,7 +149,7 @@ func TestConnClosePeerCloses(t *testing.T) {
|
||||
reason: "because",
|
||||
})
|
||||
|
||||
tc.advance(1 * time.Second) // long enough to exit the draining state
|
||||
time.Sleep(1 * time.Second) // long enough to exit the draining state
|
||||
qr.wantEvents(t, jsonEvent{
|
||||
"name": "connectivity:connection_closed",
|
||||
"data": map[string]any{
|
||||
@@ -147,6 +159,9 @@ func TestConnClosePeerCloses(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseReceiveInInitial(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseReceiveInInitial)
|
||||
}
|
||||
func testConnCloseReceiveInInitial(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.wantFrame("client sends Initial CRYPTO frame",
|
||||
packetTypeInitial, debugFrameCrypto{
|
||||
@@ -171,6 +186,9 @@ func TestConnCloseReceiveInInitial(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseReceiveInHandshake(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseReceiveInHandshake)
|
||||
}
|
||||
func testConnCloseReceiveInHandshake(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
tc.wantFrame("client sends Initial CRYPTO frame",
|
||||
@@ -204,6 +222,9 @@ func TestConnCloseReceiveInHandshake(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseClosedByEndpoint(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseClosedByEndpoint)
|
||||
}
|
||||
func testConnCloseClosedByEndpoint(t *testing.T) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
@@ -231,6 +252,9 @@ func testConnCloseUnblocks(t *testing.T, f func(context.Context, *testConn) erro
|
||||
}
|
||||
|
||||
func TestConnCloseUnblocksAcceptStream(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseUnblocksAcceptStream)
|
||||
}
|
||||
func testConnCloseUnblocksAcceptStream(t *testing.T) {
|
||||
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
|
||||
_, err := tc.conn.AcceptStream(ctx)
|
||||
return err
|
||||
@@ -238,6 +262,9 @@ func TestConnCloseUnblocksAcceptStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseUnblocksNewStream(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseUnblocksNewStream)
|
||||
}
|
||||
func testConnCloseUnblocksNewStream(t *testing.T) {
|
||||
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
|
||||
_, err := tc.conn.NewStream(ctx)
|
||||
return err
|
||||
@@ -245,6 +272,9 @@ func TestConnCloseUnblocksNewStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseUnblocksStreamRead(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseUnblocksStreamRead)
|
||||
}
|
||||
func testConnCloseUnblocksStreamRead(t *testing.T) {
|
||||
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
|
||||
s := newLocalStream(t, tc, bidiStream)
|
||||
s.SetReadContext(ctx)
|
||||
@@ -255,6 +285,9 @@ func TestConnCloseUnblocksStreamRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseUnblocksStreamWrite(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseUnblocksStreamWrite)
|
||||
}
|
||||
func testConnCloseUnblocksStreamWrite(t *testing.T) {
|
||||
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
|
||||
s := newLocalStream(t, tc, bidiStream)
|
||||
s.SetWriteContext(ctx)
|
||||
@@ -267,6 +300,9 @@ func TestConnCloseUnblocksStreamWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCloseUnblocksStreamClose(t *testing.T) {
|
||||
synctest.Test(t, testConnCloseUnblocksStreamClose)
|
||||
}
|
||||
func testConnCloseUnblocksStreamClose(t *testing.T) {
|
||||
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
|
||||
s := newLocalStream(t, tc, bidiStream)
|
||||
s.SetWriteContext(ctx)
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestConnInflowReturnOnRead(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowReturnOnRead)
|
||||
}
|
||||
func testConnInflowReturnOnRead(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
|
||||
c.MaxConnReadBufferSize = 64
|
||||
})
|
||||
@@ -41,6 +47,9 @@ func TestConnInflowReturnOnRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInflowReturnOnRacingReads(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowReturnOnRacingReads)
|
||||
}
|
||||
func testConnInflowReturnOnRacingReads(t *testing.T) {
|
||||
// Perform two reads at the same time,
|
||||
// one for half of MaxConnReadBufferSize
|
||||
// and one for one byte.
|
||||
@@ -91,6 +100,9 @@ func TestConnInflowReturnOnRacingReads(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInflowReturnOnClose(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowReturnOnClose)
|
||||
}
|
||||
func testConnInflowReturnOnClose(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
|
||||
c.MaxConnReadBufferSize = 64
|
||||
})
|
||||
@@ -107,6 +119,9 @@ func TestConnInflowReturnOnClose(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInflowReturnOnReset(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowReturnOnReset)
|
||||
}
|
||||
func testConnInflowReturnOnReset(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
|
||||
c.MaxConnReadBufferSize = 64
|
||||
})
|
||||
@@ -127,6 +142,9 @@ func TestConnInflowReturnOnReset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInflowStreamViolation(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowStreamViolation)
|
||||
}
|
||||
func testConnInflowStreamViolation(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
c.MaxConnReadBufferSize = 100
|
||||
})
|
||||
@@ -169,6 +187,9 @@ func TestConnInflowStreamViolation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInflowResetViolation(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowResetViolation)
|
||||
}
|
||||
func testConnInflowResetViolation(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
c.MaxConnReadBufferSize = 100
|
||||
})
|
||||
@@ -197,6 +218,9 @@ func TestConnInflowResetViolation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInflowMultipleStreams(t *testing.T) {
|
||||
synctest.Test(t, testConnInflowMultipleStreams)
|
||||
}
|
||||
func testConnInflowMultipleStreams(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
c.MaxConnReadBufferSize = 128
|
||||
})
|
||||
@@ -247,6 +271,9 @@ func TestConnInflowMultipleStreams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnOutflowBlocked(t *testing.T) {
|
||||
synctest.Test(t, testConnOutflowBlocked)
|
||||
}
|
||||
func testConnOutflowBlocked(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
|
||||
permissiveTransportParameters,
|
||||
func(p *transportParameters) {
|
||||
@@ -291,6 +318,9 @@ func TestConnOutflowBlocked(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnOutflowMaxDataDecreases(t *testing.T) {
|
||||
synctest.Test(t, testConnOutflowMaxDataDecreases)
|
||||
}
|
||||
func testConnOutflowMaxDataDecreases(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
|
||||
permissiveTransportParameters,
|
||||
func(p *transportParameters) {
|
||||
@@ -318,6 +348,9 @@ func TestConnOutflowMaxDataDecreases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnOutflowMaxDataRoundRobin(t *testing.T) {
|
||||
synctest.Test(t, testConnOutflowMaxDataRoundRobin)
|
||||
}
|
||||
func testConnOutflowMaxDataRoundRobin(t *testing.T) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, clientSide, permissiveTransportParameters,
|
||||
func(p *transportParameters) {
|
||||
@@ -370,6 +403,9 @@ func TestConnOutflowMaxDataRoundRobin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnOutflowMetaAndData(t *testing.T) {
|
||||
synctest.Test(t, testConnOutflowMetaAndData)
|
||||
}
|
||||
func testConnOutflowMetaAndData(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
|
||||
permissiveTransportParameters,
|
||||
func(p *transportParameters) {
|
||||
@@ -398,6 +434,9 @@ func TestConnOutflowMetaAndData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnOutflowResentData(t *testing.T) {
|
||||
synctest.Test(t, testConnOutflowResentData)
|
||||
}
|
||||
func testConnOutflowResentData(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
|
||||
permissiveTransportParameters,
|
||||
func(p *transportParameters) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -11,9 +13,13 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestConnIDClientHandshake(t *testing.T) {
|
||||
synctest.Test(t, testConnIDClientHandshake)
|
||||
}
|
||||
func testConnIDClientHandshake(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
// On initialization, the client chooses local and remote IDs.
|
||||
//
|
||||
@@ -57,6 +63,9 @@ func TestConnIDClientHandshake(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDServerHandshake(t *testing.T) {
|
||||
synctest.Test(t, testConnIDServerHandshake)
|
||||
}
|
||||
func testConnIDServerHandshake(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
// On initialization, the server is provided with the client-chosen
|
||||
// transient connection ID, and allocates an ID of its own.
|
||||
@@ -178,6 +187,9 @@ func TestNewRandomConnID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerRequestsManyIDs(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerRequestsManyIDs)
|
||||
}
|
||||
func testConnIDPeerRequestsManyIDs(t *testing.T) {
|
||||
// "An endpoint SHOULD ensure that its peer has a sufficient number
|
||||
// of available and unused connection IDs."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
|
||||
@@ -220,6 +232,9 @@ func TestConnIDPeerRequestsManyIDs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerProvidesTooManyIDs(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerProvidesTooManyIDs)
|
||||
}
|
||||
func testConnIDPeerProvidesTooManyIDs(t *testing.T) {
|
||||
// "An endpoint MUST NOT provide more connection IDs than the peer's limit."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
|
||||
tc := newTestConn(t, serverSide)
|
||||
@@ -238,6 +253,9 @@ func TestConnIDPeerProvidesTooManyIDs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerTemporarilyExceedsActiveConnIDLimit)
|
||||
}
|
||||
func testConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) {
|
||||
// "An endpoint MAY send connection IDs that temporarily exceed a peer's limit
|
||||
// if the NEW_CONNECTION_ID frame also requires the retirement of any excess [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
|
||||
@@ -272,7 +290,7 @@ func TestConnIDPeerRetiresConnID(t *testing.T) {
|
||||
clientSide,
|
||||
serverSide,
|
||||
} {
|
||||
t.Run(side.String(), func(t *testing.T) {
|
||||
synctestSubtest(t, side.String(), func(t *testing.T) {
|
||||
tc := newTestConn(t, side)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
@@ -293,6 +311,9 @@ func TestConnIDPeerRetiresConnID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerWithZeroLengthConnIDSendsNewConnectionID)
|
||||
}
|
||||
func testConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
|
||||
// "An endpoint that selects a zero-length connection ID during the handshake
|
||||
// cannot issue a new connection ID."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-8
|
||||
@@ -315,6 +336,9 @@ func TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerRequestsRetirement(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerRequestsRetirement)
|
||||
}
|
||||
func testConnIDPeerRequestsRetirement(t *testing.T) {
|
||||
// "Upon receipt of an increased Retire Prior To field, the peer MUST
|
||||
// stop using the corresponding connection IDs and retire them with
|
||||
// RETIRE_CONNECTION_ID frames [...]"
|
||||
@@ -339,6 +363,9 @@ func TestConnIDPeerRequestsRetirement(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerDoesNotAcknowledgeRetirement)
|
||||
}
|
||||
func testConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
|
||||
// "An endpoint SHOULD limit the number of connection IDs it has retired locally
|
||||
// for which RETIRE_CONNECTION_ID frames have not yet been acknowledged."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6
|
||||
@@ -364,6 +391,9 @@ func TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
|
||||
synctest.Test(t, testConnIDRepeatedNewConnectionIDFrame)
|
||||
}
|
||||
func testConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
|
||||
// "Receipt of the same [NEW_CONNECTION_ID] frame multiple times
|
||||
// MUST NOT be treated as a connection error.
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-19.15-7
|
||||
@@ -387,6 +417,9 @@ func TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDForSequenceNumberChanges(t *testing.T) {
|
||||
synctest.Test(t, testConnIDForSequenceNumberChanges)
|
||||
}
|
||||
func testConnIDForSequenceNumberChanges(t *testing.T) {
|
||||
// "[...] if a sequence number is used for different connection IDs,
|
||||
// the endpoint MAY treat that receipt as a connection error
|
||||
// of type PROTOCOL_VIOLATION."
|
||||
@@ -415,6 +448,9 @@ func TestConnIDForSequenceNumberChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDRetirePriorToAfterNewConnID(t *testing.T) {
|
||||
synctest.Test(t, testConnIDRetirePriorToAfterNewConnID)
|
||||
}
|
||||
func testConnIDRetirePriorToAfterNewConnID(t *testing.T) {
|
||||
// "Receiving a value in the Retire Prior To field that is greater than
|
||||
// that in the Sequence Number field MUST be treated as a connection error
|
||||
// of type FRAME_ENCODING_ERROR.
|
||||
@@ -436,6 +472,9 @@ func TestConnIDRetirePriorToAfterNewConnID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDAlreadyRetired(t *testing.T) {
|
||||
synctest.Test(t, testConnIDAlreadyRetired)
|
||||
}
|
||||
func testConnIDAlreadyRetired(t *testing.T) {
|
||||
// "An endpoint that receives a NEW_CONNECTION_ID frame with a
|
||||
// sequence number smaller than the Retire Prior To field of a
|
||||
// previously received NEW_CONNECTION_ID frame MUST send a
|
||||
@@ -472,6 +511,9 @@ func TestConnIDAlreadyRetired(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
|
||||
synctest.Test(t, testConnIDRepeatedRetireConnectionIDFrame)
|
||||
}
|
||||
func testConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
@@ -493,6 +535,9 @@ func TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDRetiredUnsent(t *testing.T) {
|
||||
synctest.Test(t, testConnIDRetiredUnsent)
|
||||
}
|
||||
func testConnIDRetiredUnsent(t *testing.T) {
|
||||
// "Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number
|
||||
// greater than any previously sent to the peer MUST be treated as a
|
||||
// connection error of type PROTOCOL_VIOLATION."
|
||||
@@ -512,6 +557,9 @@ func TestConnIDRetiredUnsent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDUsePreferredAddressConnID(t *testing.T) {
|
||||
synctest.Test(t, testConnIDUsePreferredAddressConnID)
|
||||
}
|
||||
func testConnIDUsePreferredAddressConnID(t *testing.T) {
|
||||
// Peer gives us a connection ID in the preferred address transport parameter.
|
||||
// We don't use the preferred address at this time, but we should use the
|
||||
// connection ID. (It isn't tied to any specific address.)
|
||||
@@ -543,6 +591,9 @@ func TestConnIDUsePreferredAddressConnID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerProvidesPreferredAddrAndTooManyConnIDs)
|
||||
}
|
||||
func testConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
|
||||
// Peer gives us more conn ids than our advertised limit,
|
||||
// including a conn id in the preferred address transport parameter.
|
||||
cid := testPeerConnID(10)
|
||||
@@ -568,6 +619,9 @@ func TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) {
|
||||
synctest.Test(t, testConnIDPeerWithZeroLengthIDProvidesPreferredAddr)
|
||||
}
|
||||
func testConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) {
|
||||
// Peer gives us more conn ids than our advertised limit,
|
||||
// including a conn id in the preferred address transport parameter.
|
||||
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
||||
@@ -596,7 +650,7 @@ func TestConnIDInitialSrcConnIDMismatch(t *testing.T) {
|
||||
// "Endpoints MUST validate that received [initial_source_connection_id]
|
||||
// parameters match received connection ID values."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-7.3-3
|
||||
testSides(t, "", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "", func(t *testing.T, side connSide) {
|
||||
tc := newTestConn(t, side, func(p *transportParameters) {
|
||||
p.initialSrcConnID = []byte("invalid")
|
||||
})
|
||||
@@ -621,7 +675,7 @@ func TestConnIDInitialSrcConnIDMismatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDsCleanedUpAfterClose(t *testing.T) {
|
||||
testSides(t, "", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "", func(t *testing.T, side connSide) {
|
||||
tc := newTestConn(t, side, func(p *transportParameters) {
|
||||
if side == clientSide {
|
||||
token := testPeerStatelessResetToken(0)
|
||||
@@ -664,6 +718,9 @@ func TestConnIDsCleanedUpAfterClose(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnIDRetiredConnIDResent(t *testing.T) {
|
||||
synctest.Test(t, testConnIDRetiredConnIDResent)
|
||||
}
|
||||
func testConnIDRetiredConnIDResent(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -9,6 +11,8 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Frames may be retransmitted either when the packet containing the frame is lost, or on PTO.
|
||||
@@ -22,6 +26,16 @@ func lostFrameTest(t *testing.T, f func(t *testing.T, pto bool)) {
|
||||
})
|
||||
}
|
||||
|
||||
func lostFrameTestSynctest(t *testing.T, f func(t *testing.T, pto bool)) {
|
||||
t.Helper()
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
t.Helper()
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
f(t, pto)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// triggerLossOrPTO causes the conn to declare the last sent packet lost,
|
||||
// or advances to the PTO timer.
|
||||
func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) {
|
||||
@@ -33,7 +47,11 @@ func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) {
|
||||
if *testVV {
|
||||
tc.t.Logf("advancing to PTO timer")
|
||||
}
|
||||
tc.advanceTo(tc.conn.loss.timer)
|
||||
var when time.Time
|
||||
tc.conn.runOnLoop(tc.t.Context(), func(now time.Time, conn *Conn) {
|
||||
when = conn.loss.timer
|
||||
})
|
||||
time.Sleep(time.Until(when))
|
||||
return
|
||||
}
|
||||
if *testVV {
|
||||
@@ -77,7 +95,7 @@ func TestLostResetStreamFrame(t *testing.T) {
|
||||
// "Cancellation of stream transmission, as carried in a RESET_STREAM frame,
|
||||
// is sent until acknowledged or until all stream data is acknowledged by the peer [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.4
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -106,7 +124,7 @@ func TestLostStopSendingFrame(t *testing.T) {
|
||||
// Technically, we can stop sending a STOP_SENDING frame if the peer sends
|
||||
// us all the data for the stream or resets it. We don't bother tracking this,
|
||||
// however, so we'll keep sending the frame until it is acked. This is harmless.
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -127,7 +145,7 @@ func TestLostStopSendingFrame(t *testing.T) {
|
||||
func TestLostCryptoFrame(t *testing.T) {
|
||||
// "Data sent in CRYPTO frames is retransmitted [...] until all data has been acknowledged."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.1
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -171,7 +189,7 @@ func TestLostCryptoFrame(t *testing.T) {
|
||||
func TestLostStreamFrameEmpty(t *testing.T) {
|
||||
// A STREAM frame opening a stream, but containing no stream data, should
|
||||
// be retransmitted if lost.
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, clientSide, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
@@ -203,7 +221,7 @@ func TestLostStreamWithData(t *testing.T) {
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.2
|
||||
//
|
||||
// TODO: Lost stream frame after RESET_STREAM
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
data := []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
|
||||
p.initialMaxStreamsUni = 1
|
||||
@@ -247,6 +265,9 @@ func TestLostStreamWithData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLostStreamPartialLoss(t *testing.T) {
|
||||
synctest.Test(t, testLostStreamPartialLoss)
|
||||
}
|
||||
func testLostStreamPartialLoss(t *testing.T) {
|
||||
// Conn sends four STREAM packets.
|
||||
// ACKs are received for the packets containing bytes 0 and 2.
|
||||
// The remaining packets are declared lost.
|
||||
@@ -295,7 +316,7 @@ func TestLostMaxDataFrame(t *testing.T) {
|
||||
// "An updated value is sent in a MAX_DATA frame if the packet
|
||||
// containing the most recently sent MAX_DATA frame is declared lost [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.7
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
const maxWindowSize = 32
|
||||
buf := make([]byte, maxWindowSize)
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
|
||||
@@ -340,7 +361,7 @@ func TestLostMaxStreamDataFrame(t *testing.T) {
|
||||
// "[...] an updated value is sent when the packet containing
|
||||
// the most recent MAX_STREAM_DATA frame for a stream is lost"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
const maxWindowSize = 32
|
||||
buf := make([]byte, maxWindowSize)
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
|
||||
@@ -387,7 +408,7 @@ func TestLostMaxStreamDataFrameAfterStreamFinReceived(t *testing.T) {
|
||||
// "An endpoint SHOULD stop sending MAX_STREAM_DATA frames when
|
||||
// the receiving part of the stream enters a "Size Known" or "Reset Recvd" state."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
const maxWindowSize = 10
|
||||
buf := make([]byte, maxWindowSize)
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
|
||||
@@ -425,7 +446,7 @@ func TestLostMaxStreamsFrameMostRecent(t *testing.T) {
|
||||
// most recent MAX_STREAMS for a stream type frame is declared lost [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.9
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
c.MaxUniRemoteStreams = 1
|
||||
@@ -469,6 +490,9 @@ func TestLostMaxStreamsFrameMostRecent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLostMaxStreamsFrameNotMostRecent(t *testing.T) {
|
||||
synctest.Test(t, testLostMaxStreamsFrameNotMostRecent)
|
||||
}
|
||||
func testLostMaxStreamsFrameNotMostRecent(t *testing.T) {
|
||||
// Send two MAX_STREAMS frames, lose the first one.
|
||||
//
|
||||
// No PTO mode for this test: The ack that causes the first frame
|
||||
@@ -514,7 +538,7 @@ func TestLostStreamDataBlockedFrame(t *testing.T) {
|
||||
// "A new [STREAM_DATA_BLOCKED] frame is sent if a packet containing
|
||||
// the most recent frame for a scope is lost [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
|
||||
p.initialMaxStreamsUni = 1
|
||||
p.initialMaxData = 1 << 20
|
||||
@@ -565,7 +589,7 @@ func TestLostStreamDataBlockedFrameAfterStreamUnblocked(t *testing.T) {
|
||||
// "A new [STREAM_DATA_BLOCKED] frame is sent [...] only while
|
||||
// the endpoint is blocked on the corresponding limit."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
|
||||
p.initialMaxStreamsUni = 1
|
||||
p.initialMaxData = 1 << 20
|
||||
@@ -607,7 +631,7 @@ func TestLostStreamDataBlockedFrameAfterStreamUnblocked(t *testing.T) {
|
||||
func TestLostNewConnectionIDFrame(t *testing.T) {
|
||||
// "New connection IDs are [...] retransmitted if the packet containing them is lost."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
@@ -637,7 +661,7 @@ func TestLostRetireConnectionIDFrame(t *testing.T) {
|
||||
// "[...] retired connection IDs are [...] retransmitted
|
||||
// if the packet containing them is lost."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
@@ -664,7 +688,7 @@ func TestLostRetireConnectionIDFrame(t *testing.T) {
|
||||
func TestLostPathResponseFrame(t *testing.T) {
|
||||
// "Responses to path validation using PATH_RESPONSE frames are sent just once."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.12
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
@@ -687,7 +711,7 @@ func TestLostPathResponseFrame(t *testing.T) {
|
||||
func TestLostHandshakeDoneFrame(t *testing.T) {
|
||||
// "The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.16
|
||||
lostFrameTest(t, func(t *testing.T, pto bool) {
|
||||
lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestConnReceiveAckForUnsentPacket(t *testing.T) {
|
||||
synctest.Test(t, testConnReceiveAckForUnsentPacket)
|
||||
}
|
||||
func testConnReceiveAckForUnsentPacket(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
tc.writeFrames(packetType1RTT,
|
||||
@@ -27,6 +33,9 @@ func TestConnReceiveAckForUnsentPacket(t *testing.T) {
|
||||
// drop state for a number space, and also contains a valid ACK frame for that space,
|
||||
// we shouldn't complain about the ACK.
|
||||
func TestConnReceiveAckForDroppedSpace(t *testing.T) {
|
||||
synctest.Test(t, testConnReceiveAckForDroppedSpace)
|
||||
}
|
||||
func testConnReceiveAckForDroppedSpace(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, permissiveTransportParameters)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
tc.ignoreFrame(frameTypeNewConnectionID)
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAckElicitingAck(t *testing.T) {
|
||||
synctest.Test(t, testAckElicitingAck)
|
||||
}
|
||||
func testAckElicitingAck(t *testing.T) {
|
||||
// "A receiver that sends only non-ack-eliciting packets [...] might not receive
|
||||
// an acknowledgment for a long period of time.
|
||||
// [...] a receiver could send a [...] ack-eliciting frame occasionally [...]
|
||||
@@ -22,7 +28,7 @@ func TestAckElicitingAck(t *testing.T) {
|
||||
tc.handshake()
|
||||
const count = 100
|
||||
for i := 0; i < count; i++ {
|
||||
tc.advance(1 * time.Millisecond)
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
tc.writeFrames(packetType1RTT,
|
||||
debugFramePing{},
|
||||
)
|
||||
@@ -38,6 +44,9 @@ func TestAckElicitingAck(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSendPacketNumberSize(t *testing.T) {
|
||||
synctest.Test(t, testSendPacketNumberSize)
|
||||
}
|
||||
func testSendPacketNumberSize(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func (c *Conn) streamsCleanup() {
|
||||
|
||||
// AcceptStream waits for and returns the next stream created by the peer.
|
||||
func (c *Conn) AcceptStream(ctx context.Context) (*Stream, error) {
|
||||
return c.streams.queue.get(ctx, c.testHooks)
|
||||
return c.streams.queue.get(ctx)
|
||||
}
|
||||
|
||||
// NewStream creates a stream.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -11,9 +13,13 @@ import (
|
||||
"math"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestStreamsCreate(t *testing.T) {
|
||||
synctest.Test(t, testStreamsCreate)
|
||||
}
|
||||
func testStreamsCreate(t *testing.T) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, clientSide, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
@@ -53,6 +59,9 @@ func TestStreamsCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsAccept(t *testing.T) {
|
||||
synctest.Test(t, testStreamsAccept)
|
||||
}
|
||||
func testStreamsAccept(t *testing.T) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
@@ -95,6 +104,9 @@ func TestStreamsAccept(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsBlockingAccept(t *testing.T) {
|
||||
synctest.Test(t, testStreamsBlockingAccept)
|
||||
}
|
||||
func testStreamsBlockingAccept(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
|
||||
@@ -124,6 +136,9 @@ func TestStreamsBlockingAccept(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsLocalStreamNotCreated(t *testing.T) {
|
||||
synctest.Test(t, testStreamsLocalStreamNotCreated)
|
||||
}
|
||||
func testStreamsLocalStreamNotCreated(t *testing.T) {
|
||||
// "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR
|
||||
// if it receives a STREAM frame for a locally initiated stream that has
|
||||
// not yet been created [...]"
|
||||
@@ -142,6 +157,9 @@ func TestStreamsLocalStreamNotCreated(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsLocalStreamClosed(t *testing.T) {
|
||||
synctest.Test(t, testStreamsLocalStreamClosed)
|
||||
}
|
||||
func testStreamsLocalStreamClosed(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream, permissiveTransportParameters)
|
||||
s.CloseWrite()
|
||||
tc.wantFrame("FIN for closed stream",
|
||||
@@ -168,6 +186,9 @@ func TestStreamsLocalStreamClosed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsStreamSendOnly(t *testing.T) {
|
||||
synctest.Test(t, testStreamsStreamSendOnly)
|
||||
}
|
||||
func testStreamsStreamSendOnly(t *testing.T) {
|
||||
// "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR
|
||||
// if it receives a STREAM frame for a locally initiated stream that has
|
||||
// not yet been created [...]"
|
||||
@@ -198,6 +219,9 @@ func TestStreamsStreamSendOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsWriteQueueFairness(t *testing.T) {
|
||||
synctest.Test(t, testStreamsWriteQueueFairness)
|
||||
}
|
||||
func testStreamsWriteQueueFairness(t *testing.T) {
|
||||
ctx := canceledContext()
|
||||
const dataLen = 1 << 20
|
||||
const numStreams = 3
|
||||
@@ -233,7 +257,7 @@ func TestStreamsWriteQueueFairness(t *testing.T) {
|
||||
}
|
||||
// Wait for the stream to finish writing whatever frames it can before
|
||||
// congestion control blocks it.
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
}
|
||||
|
||||
sent := make([]int64, len(streams))
|
||||
@@ -344,7 +368,7 @@ func TestStreamsShutdown(t *testing.T) {
|
||||
},
|
||||
}} {
|
||||
name := fmt.Sprintf("%v/%v/%v", test.side, test.styp, test.name)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
synctestSubtest(t, name, func(t *testing.T) {
|
||||
tc, s := newTestConnAndStream(t, serverSide, test.side, test.styp,
|
||||
permissiveTransportParameters)
|
||||
tc.ignoreFrame(frameTypeStreamBase)
|
||||
@@ -364,6 +388,9 @@ func TestStreamsShutdown(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsCreateAndCloseRemote(t *testing.T) {
|
||||
synctest.Test(t, testStreamsCreateAndCloseRemote)
|
||||
}
|
||||
func testStreamsCreateAndCloseRemote(t *testing.T) {
|
||||
// This test exercises creating new streams in response to frames
|
||||
// from the peer, and cleaning up after streams are fully closed.
|
||||
//
|
||||
@@ -473,6 +500,9 @@ func TestStreamsCreateAndCloseRemote(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsCreateConcurrency(t *testing.T) {
|
||||
synctest.Test(t, testStreamsCreateConcurrency)
|
||||
}
|
||||
func testStreamsCreateConcurrency(t *testing.T) {
|
||||
cli, srv := newLocalConnPair(t, &Config{}, &Config{})
|
||||
|
||||
srvdone := make(chan int)
|
||||
@@ -520,6 +550,9 @@ func TestStreamsCreateConcurrency(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamsPTOWithImplicitStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamsPTOWithImplicitStream)
|
||||
}
|
||||
func testStreamsPTOWithImplicitStream(t *testing.T) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, serverSide, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -17,6 +19,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/quic/qlog"
|
||||
@@ -27,7 +30,8 @@ var (
|
||||
qlogdir = flag.String("qlog", "", "write qlog logs to directory")
|
||||
)
|
||||
|
||||
func TestConnTestConn(t *testing.T) {
|
||||
func TestConnTestConn(t *testing.T) { synctest.Test(t, testConnTestConn) }
|
||||
func testConnTestConn(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
if got, want := tc.timeUntilEvent(), defaultMaxIdleTimeout; got != want {
|
||||
@@ -40,13 +44,13 @@ func TestConnTestConn(t *testing.T) {
|
||||
})
|
||||
return
|
||||
}).result()
|
||||
if !ranAt.Equal(tc.endpoint.now) {
|
||||
t.Errorf("func ran on loop at %v, want %v", ranAt, tc.endpoint.now)
|
||||
if !ranAt.Equal(time.Now()) {
|
||||
t.Errorf("func ran on loop at %v, want %v", ranAt, time.Now())
|
||||
}
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
|
||||
nextTime := tc.endpoint.now.Add(defaultMaxIdleTimeout / 2)
|
||||
tc.advanceTo(nextTime)
|
||||
nextTime := time.Now().Add(defaultMaxIdleTimeout / 2)
|
||||
time.Sleep(time.Until(nextTime))
|
||||
ranAt, _ = runAsync(tc, func(ctx context.Context) (when time.Time, _ error) {
|
||||
tc.conn.runOnLoop(ctx, func(now time.Time, c *Conn) {
|
||||
when = now
|
||||
@@ -56,7 +60,7 @@ func TestConnTestConn(t *testing.T) {
|
||||
if !ranAt.Equal(nextTime) {
|
||||
t.Errorf("func ran on loop at %v, want %v", ranAt, nextTime)
|
||||
}
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
|
||||
tc.advanceToTimer()
|
||||
if got := tc.conn.lifetime.state; got != connStateDone {
|
||||
@@ -125,12 +129,9 @@ const maxTestKeyPhases = 3
|
||||
// A testConn is a Conn whose external interactions (sending and receiving packets,
|
||||
// setting timers) can be manipulated in tests.
|
||||
type testConn struct {
|
||||
t *testing.T
|
||||
conn *Conn
|
||||
endpoint *testEndpoint
|
||||
timer time.Time
|
||||
timerLastFired time.Time
|
||||
idlec chan struct{} // only accessed on the conn's loop
|
||||
t *testing.T
|
||||
conn *Conn
|
||||
endpoint *testEndpoint
|
||||
|
||||
// Keys are distinct from the conn's keys,
|
||||
// because the test may know about keys before the conn does.
|
||||
@@ -183,8 +184,6 @@ type testConn struct {
|
||||
// Values to set in packets sent to the conn.
|
||||
sendKeyNumber int
|
||||
sendKeyPhaseBit bool
|
||||
|
||||
asyncTestState
|
||||
}
|
||||
|
||||
type test1RTTKeys struct {
|
||||
@@ -198,10 +197,6 @@ type keySecret struct {
|
||||
}
|
||||
|
||||
// newTestConn creates a Conn for testing.
|
||||
//
|
||||
// The Conn's event loop is controlled by the test,
|
||||
// allowing test code to access Conn state directly
|
||||
// by first ensuring the loop goroutine is idle.
|
||||
func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
|
||||
t.Helper()
|
||||
config := &Config{
|
||||
@@ -242,7 +237,7 @@ func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
|
||||
endpoint.configTransportParams = configTransportParams
|
||||
endpoint.configTestConn = configTestConn
|
||||
conn, err := endpoint.e.newConn(
|
||||
endpoint.now,
|
||||
time.Now(),
|
||||
config,
|
||||
side,
|
||||
cids,
|
||||
@@ -252,7 +247,7 @@ func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc := endpoint.conns[conn]
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
return tc
|
||||
}
|
||||
|
||||
@@ -306,76 +301,33 @@ func newTestConnForConn(t *testing.T, endpoint *testEndpoint, conn *Conn) *testC
|
||||
return tc
|
||||
}
|
||||
|
||||
// advance causes time to pass.
|
||||
func (tc *testConn) advance(d time.Duration) {
|
||||
tc.t.Helper()
|
||||
tc.endpoint.advance(d)
|
||||
}
|
||||
|
||||
// advanceTo sets the current time.
|
||||
func (tc *testConn) advanceTo(now time.Time) {
|
||||
tc.t.Helper()
|
||||
tc.endpoint.advanceTo(now)
|
||||
}
|
||||
|
||||
// advanceToTimer sets the current time to the time of the Conn's next timer event.
|
||||
func (tc *testConn) advanceToTimer() {
|
||||
if tc.timer.IsZero() {
|
||||
when := tc.nextEvent()
|
||||
if when.IsZero() {
|
||||
tc.t.Fatalf("advancing to timer, but timer is not set")
|
||||
}
|
||||
tc.advanceTo(tc.timer)
|
||||
}
|
||||
|
||||
func (tc *testConn) timerDelay() time.Duration {
|
||||
if tc.timer.IsZero() {
|
||||
return math.MaxInt64 // infinite
|
||||
}
|
||||
if tc.timer.Before(tc.endpoint.now) {
|
||||
return 0
|
||||
}
|
||||
return tc.timer.Sub(tc.endpoint.now)
|
||||
time.Sleep(time.Until(when))
|
||||
synctest.Wait()
|
||||
}
|
||||
|
||||
const infiniteDuration = time.Duration(math.MaxInt64)
|
||||
|
||||
// timeUntilEvent returns the amount of time until the next connection event.
|
||||
func (tc *testConn) timeUntilEvent() time.Duration {
|
||||
if tc.timer.IsZero() {
|
||||
next := tc.nextEvent()
|
||||
if next.IsZero() {
|
||||
return infiniteDuration
|
||||
}
|
||||
if tc.timer.Before(tc.endpoint.now) {
|
||||
return 0
|
||||
}
|
||||
return tc.timer.Sub(tc.endpoint.now)
|
||||
return max(0, time.Until(next))
|
||||
}
|
||||
|
||||
// wait blocks until the conn becomes idle.
|
||||
// The conn is idle when it is blocked waiting for a packet to arrive or a timer to expire.
|
||||
// Tests shouldn't need to call wait directly.
|
||||
// testConn methods that wake the Conn event loop will call wait for them.
|
||||
func (tc *testConn) wait() {
|
||||
tc.t.Helper()
|
||||
idlec := make(chan struct{})
|
||||
fail := false
|
||||
tc.conn.sendMsg(func(now time.Time, c *Conn) {
|
||||
if tc.idlec != nil {
|
||||
tc.t.Errorf("testConn.wait called concurrently")
|
||||
fail = true
|
||||
close(idlec)
|
||||
} else {
|
||||
// nextMessage will close idlec.
|
||||
tc.idlec = idlec
|
||||
}
|
||||
func (tc *testConn) nextEvent() time.Time {
|
||||
nextc := make(chan time.Time)
|
||||
tc.conn.sendMsg(func(now, next time.Time, c *Conn) {
|
||||
nextc <- next
|
||||
})
|
||||
select {
|
||||
case <-idlec:
|
||||
case <-tc.conn.donec:
|
||||
// We may have async ops that can proceed now that the conn is done.
|
||||
tc.wakeAsync()
|
||||
}
|
||||
if fail {
|
||||
panic(fail)
|
||||
}
|
||||
return <-nextc
|
||||
}
|
||||
|
||||
func (tc *testConn) cleanup() {
|
||||
@@ -498,7 +450,7 @@ func (tc *testConn) ignoreFrame(frameType byte) {
|
||||
// It returns nil if the Conn has no more datagrams to send at this time.
|
||||
func (tc *testConn) readDatagram() *testDatagram {
|
||||
tc.t.Helper()
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
tc.sentPackets = nil
|
||||
tc.sentFrames = nil
|
||||
buf := tc.endpoint.read()
|
||||
@@ -1103,48 +1055,10 @@ func (tc *testConnHooks) handleTLSEvent(e tls.QUICEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
// nextMessage is called by the Conn's event loop to request its next event.
|
||||
func (tc *testConnHooks) nextMessage(msgc chan any, timer time.Time) (now time.Time, m any) {
|
||||
tc.timer = timer
|
||||
for {
|
||||
if !timer.IsZero() && !timer.After(tc.endpoint.now) {
|
||||
if timer.Equal(tc.timerLastFired) {
|
||||
// If the connection timer fires at time T, the Conn should take some
|
||||
// action to advance the timer into the future. If the Conn reschedules
|
||||
// the timer for the same time, it isn't making progress and we have a bug.
|
||||
tc.t.Errorf("connection timer spinning; now=%v timer=%v", tc.endpoint.now, timer)
|
||||
} else {
|
||||
tc.timerLastFired = timer
|
||||
return tc.endpoint.now, timerEvent{}
|
||||
}
|
||||
}
|
||||
select {
|
||||
case m := <-msgc:
|
||||
return tc.endpoint.now, m
|
||||
default:
|
||||
}
|
||||
if !tc.wakeAsync() {
|
||||
break
|
||||
}
|
||||
}
|
||||
// If the message queue is empty, then the conn is idle.
|
||||
if tc.idlec != nil {
|
||||
idlec := tc.idlec
|
||||
tc.idlec = nil
|
||||
close(idlec)
|
||||
}
|
||||
m = <-msgc
|
||||
return tc.endpoint.now, m
|
||||
}
|
||||
|
||||
func (tc *testConnHooks) newConnID(seq int64) ([]byte, error) {
|
||||
return testLocalConnID(seq), nil
|
||||
}
|
||||
|
||||
func (tc *testConnHooks) timeNow() time.Time {
|
||||
return tc.endpoint.now
|
||||
}
|
||||
|
||||
// testLocalConnID returns the connection ID with a given sequence number
|
||||
// used by a Conn under test.
|
||||
func testLocalConnID(seq int64) []byte {
|
||||
|
||||
@@ -36,7 +36,6 @@ type Endpoint struct {
|
||||
}
|
||||
|
||||
type endpointTestHooks interface {
|
||||
timeNow() time.Time
|
||||
newConn(c *Conn)
|
||||
}
|
||||
|
||||
@@ -160,7 +159,7 @@ func (e *Endpoint) Close(ctx context.Context) error {
|
||||
|
||||
// Accept waits for and returns the next connection.
|
||||
func (e *Endpoint) Accept(ctx context.Context) (*Conn, error) {
|
||||
return e.acceptQueue.get(ctx, nil)
|
||||
return e.acceptQueue.get(ctx)
|
||||
}
|
||||
|
||||
// Dial creates and returns a connection to a network address.
|
||||
@@ -269,12 +268,7 @@ func (e *Endpoint) handleUnknownDestinationDatagram(m *datagram) {
|
||||
if len(m.b) < minimumValidPacketSize {
|
||||
return
|
||||
}
|
||||
var now time.Time
|
||||
if e.testHooks != nil {
|
||||
now = e.testHooks.timeNow()
|
||||
} else {
|
||||
now = time.Now()
|
||||
}
|
||||
now := time.Now()
|
||||
// Check to see if this is a stateless reset.
|
||||
var token statelessResetToken
|
||||
copy(token[:], m.b[len(m.b)-len(token):])
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -12,8 +14,9 @@ import (
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
"testing/synctest"
|
||||
|
||||
"golang.org/x/net/quic/qlog"
|
||||
)
|
||||
@@ -126,22 +129,22 @@ func makeTestConfig(conf *Config, side connSide) *Config {
|
||||
type testEndpoint struct {
|
||||
t *testing.T
|
||||
e *Endpoint
|
||||
now time.Time
|
||||
recvc chan *datagram
|
||||
idlec chan struct{}
|
||||
conns map[*Conn]*testConn
|
||||
acceptQueue []*testConn
|
||||
configTransportParams []func(*transportParameters)
|
||||
configTestConn []func(*testConn)
|
||||
sentDatagrams [][]byte
|
||||
peerTLSConn *tls.QUICConn
|
||||
lastInitialDstConnID []byte // for parsing Retry packets
|
||||
|
||||
sentDatagramsMu sync.Mutex
|
||||
sentDatagrams [][]byte
|
||||
}
|
||||
|
||||
func newTestEndpoint(t *testing.T, config *Config) *testEndpoint {
|
||||
te := &testEndpoint{
|
||||
t: t,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
recvc: make(chan *datagram),
|
||||
idlec: make(chan struct{}),
|
||||
conns: make(map[*Conn]*testConn),
|
||||
@@ -159,16 +162,6 @@ func (te *testEndpoint) cleanup() {
|
||||
te.e.Close(canceledContext())
|
||||
}
|
||||
|
||||
func (te *testEndpoint) wait() {
|
||||
select {
|
||||
case te.idlec <- struct{}{}:
|
||||
case <-te.e.closec:
|
||||
}
|
||||
for _, tc := range te.conns {
|
||||
tc.wait()
|
||||
}
|
||||
}
|
||||
|
||||
// accept returns a server connection from the endpoint.
|
||||
// Unlike Endpoint.Accept, connections are available as soon as they are created.
|
||||
func (te *testEndpoint) accept() *testConn {
|
||||
@@ -182,7 +175,7 @@ func (te *testEndpoint) accept() *testConn {
|
||||
|
||||
func (te *testEndpoint) write(d *datagram) {
|
||||
te.recvc <- d
|
||||
te.wait()
|
||||
synctest.Wait()
|
||||
}
|
||||
|
||||
var testClientAddr = netip.MustParseAddrPort("10.0.0.1:8000")
|
||||
@@ -241,7 +234,9 @@ func (te *testEndpoint) connForSource(srcConnID []byte) *testConn {
|
||||
|
||||
func (te *testEndpoint) read() []byte {
|
||||
te.t.Helper()
|
||||
te.wait()
|
||||
synctest.Wait()
|
||||
te.sentDatagramsMu.Lock()
|
||||
defer te.sentDatagramsMu.Unlock()
|
||||
if len(te.sentDatagrams) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -279,34 +274,9 @@ func (te *testEndpoint) wantIdle(expectation string) {
|
||||
}
|
||||
}
|
||||
|
||||
// advance causes time to pass.
|
||||
func (te *testEndpoint) advance(d time.Duration) {
|
||||
te.t.Helper()
|
||||
te.advanceTo(te.now.Add(d))
|
||||
}
|
||||
|
||||
// advanceTo sets the current time.
|
||||
func (te *testEndpoint) advanceTo(now time.Time) {
|
||||
te.t.Helper()
|
||||
if te.now.After(now) {
|
||||
te.t.Fatalf("time moved backwards: %v -> %v", te.now, now)
|
||||
}
|
||||
te.now = now
|
||||
for _, tc := range te.conns {
|
||||
if !tc.timer.After(te.now) {
|
||||
tc.conn.sendMsg(timerEvent{})
|
||||
tc.wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testEndpointHooks implements endpointTestHooks.
|
||||
type testEndpointHooks testEndpoint
|
||||
|
||||
func (te *testEndpointHooks) timeNow() time.Time {
|
||||
return te.now
|
||||
}
|
||||
|
||||
func (te *testEndpointHooks) newConn(c *Conn) {
|
||||
tc := newTestConnForConn(te.t, (*testEndpoint)(te), c)
|
||||
te.conns[c] = tc
|
||||
@@ -338,6 +308,8 @@ func (te *testEndpointUDPConn) Read(f func(*datagram)) {
|
||||
}
|
||||
|
||||
func (te *testEndpointUDPConn) Write(dgram datagram) error {
|
||||
te.sentDatagramsMu.Lock()
|
||||
defer te.sentDatagramsMu.Unlock()
|
||||
te.sentDatagrams = append(te.sentDatagrams, append([]byte(nil), dgram.b...))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,10 +46,7 @@ func (g *gate) lock() (set bool) {
|
||||
|
||||
// waitAndLock waits until the condition is set before acquiring the gate.
|
||||
// If the context expires, waitAndLock returns an error and does not acquire the gate.
|
||||
func (g *gate) waitAndLock(ctx context.Context, testHooks connTestHooks) error {
|
||||
if testHooks != nil {
|
||||
return testHooks.waitUntil(ctx, g.lockIfSet)
|
||||
}
|
||||
func (g *gate) waitAndLock(ctx context.Context) error {
|
||||
select {
|
||||
case <-g.set:
|
||||
return nil
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestGateWaitAndLockContext(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
if err := g.waitAndLock(ctx, nil); err != context.Canceled {
|
||||
if err := g.waitAndLock(ctx); err != context.Canceled {
|
||||
t.Errorf("g.waitAndLock() = %v, want context.Canceled", err)
|
||||
}
|
||||
// waitAndLock succeeds
|
||||
@@ -58,7 +58,7 @@ func TestGateWaitAndLockContext(t *testing.T) {
|
||||
set = true
|
||||
g.unlock(true)
|
||||
}()
|
||||
if err := g.waitAndLock(context.Background(), nil); err != nil {
|
||||
if err := g.waitAndLock(context.Background()); err != nil {
|
||||
t.Errorf("g.waitAndLock() = %v, want nil", err)
|
||||
}
|
||||
if !set {
|
||||
@@ -66,7 +66,7 @@ func TestGateWaitAndLockContext(t *testing.T) {
|
||||
}
|
||||
g.unlock(true)
|
||||
// waitAndLock succeeds when the gate is set and the context is canceled
|
||||
if err := g.waitAndLock(ctx, nil); err != nil {
|
||||
if err := g.waitAndLock(ctx); err != nil {
|
||||
t.Errorf("g.waitAndLock() = %v, want nil", err)
|
||||
}
|
||||
}
|
||||
@@ -89,5 +89,5 @@ func TestGateUnlockFunc(t *testing.T) {
|
||||
g.lock()
|
||||
defer g.unlockFunc(func() bool { return true })
|
||||
}()
|
||||
g.waitAndLock(context.Background(), nil)
|
||||
g.waitAndLock(context.Background())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -9,10 +11,14 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHandshakeTimeoutExpiresServer(t *testing.T) {
|
||||
synctest.Test(t, testHandshakeTimeoutExpiresServer)
|
||||
}
|
||||
func testHandshakeTimeoutExpiresServer(t *testing.T) {
|
||||
const timeout = 5 * time.Second
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
c.HandshakeTimeout = timeout
|
||||
@@ -32,18 +38,18 @@ func TestHandshakeTimeoutExpiresServer(t *testing.T) {
|
||||
packetTypeHandshake, debugFrameCrypto{})
|
||||
tc.writeAckForAll()
|
||||
|
||||
if got, want := tc.timerDelay(), timeout; got != want {
|
||||
if got, want := tc.timeUntilEvent(), timeout; got != want {
|
||||
t.Errorf("connection timer = %v, want %v (handshake timeout)", got, want)
|
||||
}
|
||||
|
||||
// Client sends a packet, but this does not extend the handshake timer.
|
||||
tc.advance(1 * time.Second)
|
||||
time.Sleep(1 * time.Second)
|
||||
tc.writeFrames(packetTypeHandshake, debugFrameCrypto{
|
||||
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][:1], // partial data
|
||||
})
|
||||
tc.wantIdle("handshake is not complete")
|
||||
|
||||
tc.advance(timeout - 1*time.Second)
|
||||
time.Sleep(timeout - 1*time.Second)
|
||||
tc.wantFrame("server closes connection after handshake timeout",
|
||||
packetTypeHandshake, debugFrameConnectionCloseTransport{
|
||||
code: errConnectionRefused,
|
||||
@@ -51,6 +57,9 @@ func TestHandshakeTimeoutExpiresServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandshakeTimeoutExpiresClient(t *testing.T) {
|
||||
synctest.Test(t, testHandshakeTimeoutExpiresClient)
|
||||
}
|
||||
func testHandshakeTimeoutExpiresClient(t *testing.T) {
|
||||
const timeout = 5 * time.Second
|
||||
tc := newTestConn(t, clientSide, func(c *Config) {
|
||||
c.HandshakeTimeout = timeout
|
||||
@@ -77,10 +86,10 @@ func TestHandshakeTimeoutExpiresClient(t *testing.T) {
|
||||
tc.writeAckForAll()
|
||||
tc.wantIdle("client is waiting for end of handshake")
|
||||
|
||||
if got, want := tc.timerDelay(), timeout; got != want {
|
||||
if got, want := tc.timeUntilEvent(), timeout; got != want {
|
||||
t.Errorf("connection timer = %v, want %v (handshake timeout)", got, want)
|
||||
}
|
||||
tc.advance(timeout)
|
||||
time.Sleep(timeout)
|
||||
tc.wantFrame("client closes connection after handshake timeout",
|
||||
packetTypeHandshake, debugFrameConnectionCloseTransport{
|
||||
code: errConnectionRefused,
|
||||
@@ -110,7 +119,7 @@ func TestIdleTimeoutExpires(t *testing.T) {
|
||||
wantTimeout: 10 * time.Second,
|
||||
}} {
|
||||
name := fmt.Sprintf("local=%v/peer=%v", test.localMaxIdleTimeout, test.peerMaxIdleTimeout)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
synctestSubtest(t, name, func(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, func(p *transportParameters) {
|
||||
p.maxIdleTimeout = test.peerMaxIdleTimeout
|
||||
}, func(c *Config) {
|
||||
@@ -120,13 +129,13 @@ func TestIdleTimeoutExpires(t *testing.T) {
|
||||
if got, want := tc.timeUntilEvent(), test.wantTimeout; got != want {
|
||||
t.Errorf("new conn timeout=%v, want %v (idle timeout)", got, want)
|
||||
}
|
||||
tc.advance(test.wantTimeout - 1)
|
||||
time.Sleep(test.wantTimeout - 1)
|
||||
tc.wantIdle("connection is idle and alive prior to timeout")
|
||||
ctx := canceledContext()
|
||||
if err := tc.conn.Wait(ctx); err != context.Canceled {
|
||||
t.Fatalf("conn.Wait() = %v, want Canceled", err)
|
||||
}
|
||||
tc.advance(1)
|
||||
time.Sleep(1)
|
||||
tc.wantIdle("connection exits after timeout")
|
||||
if err := tc.conn.Wait(ctx); err != errIdleTimeout {
|
||||
t.Fatalf("conn.Wait() = %v, want errIdleTimeout", err)
|
||||
@@ -154,7 +163,7 @@ func TestIdleTimeoutKeepAlive(t *testing.T) {
|
||||
wantTimeout: 30 * time.Second,
|
||||
}} {
|
||||
name := fmt.Sprintf("idle_timeout=%v/keepalive=%v", test.idleTimeout, test.keepAlive)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
synctestSubtest(t, name, func(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
c.MaxIdleTimeout = test.idleTimeout
|
||||
c.KeepAlivePeriod = test.keepAlive
|
||||
@@ -163,9 +172,9 @@ func TestIdleTimeoutKeepAlive(t *testing.T) {
|
||||
if got, want := tc.timeUntilEvent(), test.wantTimeout; got != want {
|
||||
t.Errorf("new conn timeout=%v, want %v (keepalive timeout)", got, want)
|
||||
}
|
||||
tc.advance(test.wantTimeout - 1)
|
||||
time.Sleep(test.wantTimeout - 1)
|
||||
tc.wantIdle("connection is idle prior to timeout")
|
||||
tc.advance(1)
|
||||
time.Sleep(1)
|
||||
tc.wantFrameType("keep-alive ping is sent", packetType1RTT,
|
||||
debugFramePing{})
|
||||
})
|
||||
@@ -173,6 +182,9 @@ func TestIdleTimeoutKeepAlive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIdleLongTermKeepAliveSent(t *testing.T) {
|
||||
synctest.Test(t, testIdleLongTermKeepAliveSent)
|
||||
}
|
||||
func testIdleLongTermKeepAliveSent(t *testing.T) {
|
||||
// This test examines a connection sitting idle and sending periodic keep-alive pings.
|
||||
const keepAlivePeriod = 30 * time.Second
|
||||
tc := newTestConn(t, clientSide, func(c *Config) {
|
||||
@@ -191,7 +203,7 @@ func TestIdleLongTermKeepAliveSent(t *testing.T) {
|
||||
if got, want := tc.timeUntilEvent(), keepAlivePeriod; got != want {
|
||||
t.Errorf("i=%v conn timeout=%v, want %v (keepalive timeout)", i, got, want)
|
||||
}
|
||||
tc.advance(keepAlivePeriod)
|
||||
time.Sleep(keepAlivePeriod)
|
||||
tc.wantFrameType("keep-alive ping is sent", packetType1RTT,
|
||||
debugFramePing{})
|
||||
tc.writeAckForAll()
|
||||
@@ -199,6 +211,9 @@ func TestIdleLongTermKeepAliveSent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIdleLongTermKeepAliveReceived(t *testing.T) {
|
||||
synctest.Test(t, testIdleLongTermKeepAliveReceived)
|
||||
}
|
||||
func testIdleLongTermKeepAliveReceived(t *testing.T) {
|
||||
// This test examines a connection sitting idle, but receiving periodic peer
|
||||
// traffic to keep the connection alive.
|
||||
const idleTimeout = 30 * time.Second
|
||||
@@ -207,7 +222,7 @@ func TestIdleLongTermKeepAliveReceived(t *testing.T) {
|
||||
})
|
||||
tc.handshake()
|
||||
for i := 0; i < 10; i++ {
|
||||
tc.advance(idleTimeout - 1*time.Second)
|
||||
time.Sleep(idleTimeout - 1*time.Second)
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
if got, want := tc.timeUntilEvent(), maxAckDelay-timerGranularity; got != want {
|
||||
t.Errorf("i=%v conn timeout=%v, want %v (max_ack_delay)", i, got, want)
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestKeyUpdatePeerUpdates(t *testing.T) {
|
||||
synctest.Test(t, testKeyUpdatePeerUpdates)
|
||||
}
|
||||
func testKeyUpdatePeerUpdates(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrames = nil // ignore nothing
|
||||
@@ -56,6 +62,9 @@ func TestKeyUpdatePeerUpdates(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
|
||||
synctest.Test(t, testKeyUpdateAcceptPreviousPhaseKeys)
|
||||
}
|
||||
func testKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
|
||||
// "An endpoint SHOULD retain old keys for some time after
|
||||
// unprotecting a packet sent using the new keys."
|
||||
// https://www.rfc-editor.org/rfc/rfc9001#section-6.1-8
|
||||
@@ -112,6 +121,9 @@ func TestKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
|
||||
synctest.Test(t, testKeyUpdateRejectPacketFromPriorPhase)
|
||||
}
|
||||
func testKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
|
||||
// "Packets with higher packet numbers MUST be protected with either
|
||||
// the same or newer packet protection keys than packets with lower packet numbers."
|
||||
// https://www.rfc-editor.org/rfc/rfc9001#section-6.4-2
|
||||
@@ -161,6 +173,9 @@ func TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKeyUpdateLocallyInitiated(t *testing.T) {
|
||||
synctest.Test(t, testKeyUpdateLocallyInitiated)
|
||||
}
|
||||
func testKeyUpdateLocallyInitiated(t *testing.T) {
|
||||
const updateAfter = 4 // initiate key update after 1-RTT packet 4
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.conn.keysAppData.updateAfter = updateAfter
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestPathChallengeReceived(t *testing.T) {
|
||||
@@ -22,30 +25,35 @@ func TestPathChallengeReceived(t *testing.T) {
|
||||
padTo: 1200,
|
||||
wantPadding: 1200,
|
||||
}} {
|
||||
// "The recipient of [a PATH_CHALLENGE] frame MUST generate
|
||||
// a PATH_RESPONSE frame [...] containing the same Data value."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.17-7
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
data := pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
|
||||
tc.writeFrames(packetType1RTT, debugFramePathChallenge{
|
||||
data: data,
|
||||
}, debugFramePadding{
|
||||
to: test.padTo,
|
||||
})
|
||||
tc.wantFrame("response to PATH_CHALLENGE",
|
||||
packetType1RTT, debugFramePathResponse{
|
||||
synctestSubtest(t, test.name, func(t *testing.T) {
|
||||
// "The recipient of [a PATH_CHALLENGE] frame MUST generate
|
||||
// a PATH_RESPONSE frame [...] containing the same Data value."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.17-7
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
data := pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
|
||||
tc.writeFrames(packetType1RTT, debugFramePathChallenge{
|
||||
data: data,
|
||||
}, debugFramePadding{
|
||||
to: test.padTo,
|
||||
})
|
||||
if got, want := tc.lastDatagram.paddedSize, test.wantPadding; got != want {
|
||||
t.Errorf("PATH_RESPONSE expanded to %v bytes, want %v", got, want)
|
||||
}
|
||||
tc.wantIdle("connection is idle")
|
||||
tc.wantFrame("response to PATH_CHALLENGE",
|
||||
packetType1RTT, debugFramePathResponse{
|
||||
data: data,
|
||||
})
|
||||
if got, want := tc.lastDatagram.paddedSize, test.wantPadding; got != want {
|
||||
t.Errorf("PATH_RESPONSE expanded to %v bytes, want %v", got, want)
|
||||
}
|
||||
tc.wantIdle("connection is idle")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathResponseMismatchReceived(t *testing.T) {
|
||||
synctest.Test(t, testPathResponseMismatchReceived)
|
||||
}
|
||||
func testPathResponseMismatchReceived(t *testing.T) {
|
||||
// "If the content of a PATH_RESPONSE frame does not match the content of
|
||||
// a PATH_CHALLENGE frame previously sent by the endpoint,
|
||||
// the endpoint MAY generate a connection error of type PROTOCOL_VIOLATION."
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
synctest.Test(t, testPing)
|
||||
}
|
||||
func testPing(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
|
||||
@@ -22,6 +30,9 @@ func TestPing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAck(t *testing.T) {
|
||||
synctest.Test(t, testAck)
|
||||
}
|
||||
func testAck(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -12,14 +14,16 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/quic/qlog"
|
||||
)
|
||||
|
||||
func TestQLogHandshake(t *testing.T) {
|
||||
testSides(t, "", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "", func(t *testing.T, side connSide) {
|
||||
qr := &qlogRecord{}
|
||||
tc := newTestConn(t, side, qr.config)
|
||||
tc.handshake()
|
||||
@@ -55,6 +59,9 @@ func TestQLogHandshake(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQLogPacketFrames(t *testing.T) {
|
||||
synctest.Test(t, testQLogPacketFrames)
|
||||
}
|
||||
func testQLogPacketFrames(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc := newTestConn(t, clientSide, qr.config)
|
||||
tc.handshake()
|
||||
@@ -111,7 +118,7 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
|
||||
tc.ignoreFrame(frameTypeCrypto)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
tc.ignoreFrame(frameTypePing)
|
||||
tc.advance(5 * time.Second)
|
||||
time.Sleep(5 * time.Second)
|
||||
},
|
||||
}, {
|
||||
trigger: "idle_timeout",
|
||||
@@ -122,7 +129,7 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
|
||||
},
|
||||
f: func(tc *testConn) {
|
||||
tc.handshake()
|
||||
tc.advance(5 * time.Second)
|
||||
time.Sleep(5 * time.Second)
|
||||
},
|
||||
}, {
|
||||
trigger: "error",
|
||||
@@ -134,7 +141,7 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
|
||||
tc.conn.Abort(nil)
|
||||
},
|
||||
}} {
|
||||
t.Run(test.trigger, func(t *testing.T) {
|
||||
synctestSubtest(t, test.trigger, func(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc := newTestConn(t, clientSide, append(test.connOpts, qr.config)...)
|
||||
test.f(tc)
|
||||
@@ -147,7 +154,7 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
|
||||
t.Fatalf("unexpected frame: %v", fr)
|
||||
}
|
||||
tc.wantIdle("connection should be idle while closing")
|
||||
tc.advance(5 * time.Second) // long enough for the drain timer to expire
|
||||
time.Sleep(5 * time.Second) // long enough for the drain timer to expire
|
||||
qr.wantEvents(t, jsonEvent{
|
||||
"name": "connectivity:connection_closed",
|
||||
"data": map[string]any{
|
||||
@@ -159,6 +166,9 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQLogRecovery(t *testing.T) {
|
||||
synctest.Test(t, testQLogRecovery)
|
||||
}
|
||||
func testQLogRecovery(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
|
||||
permissiveTransportParameters, qr.config)
|
||||
@@ -198,6 +208,9 @@ func TestQLogRecovery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQLogLoss(t *testing.T) {
|
||||
synctest.Test(t, testQLogLoss)
|
||||
}
|
||||
func testQLogLoss(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
|
||||
permissiveTransportParameters, qr.config)
|
||||
@@ -230,6 +243,9 @@ func TestQLogLoss(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQLogPacketDropped(t *testing.T) {
|
||||
synctest.Test(t, testQLogPacketDropped)
|
||||
}
|
||||
func testQLogPacketDropped(t *testing.T) {
|
||||
qr := &qlogRecord{}
|
||||
tc := newTestConn(t, clientSide, permissiveTransportParameters, qr.config)
|
||||
tc.handshake()
|
||||
@@ -324,10 +340,13 @@ func jsonPartialEqual(got, want any) (equal bool) {
|
||||
|
||||
// A qlogRecord records events.
|
||||
type qlogRecord struct {
|
||||
mu sync.Mutex
|
||||
ev []jsonEvent
|
||||
}
|
||||
|
||||
func (q *qlogRecord) Write(b []byte) (int, error) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
// This relies on the property that the Handler always makes one Write call per event.
|
||||
if len(b) < 1 || b[0] != 0x1e {
|
||||
panic(fmt.Errorf("trace Write should start with record separator, got %q", string(b)))
|
||||
@@ -355,6 +374,8 @@ func (q *qlogRecord) config(c *Config) {
|
||||
// wantEvents checks that every event in want occurs in the order specified.
|
||||
func (q *qlogRecord) wantEvents(t *testing.T, want ...jsonEvent) {
|
||||
t.Helper()
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
got := q.ev
|
||||
if !jsonPartialEqual(got, want) {
|
||||
t.Fatalf("got events:\n%v\n\nwant events:\n%v", got, want)
|
||||
|
||||
@@ -42,9 +42,9 @@ func (q *queue[T]) put(v T) bool {
|
||||
|
||||
// get removes the first item from the queue, blocking until ctx is done, an item is available,
|
||||
// or the queue is closed.
|
||||
func (q *queue[T]) get(ctx context.Context, testHooks connTestHooks) (T, error) {
|
||||
func (q *queue[T]) get(ctx context.Context) (T, error) {
|
||||
var zero T
|
||||
if err := q.gate.waitAndLock(ctx, testHooks); err != nil {
|
||||
if err := q.gate.waitAndLock(ctx); err != nil {
|
||||
return zero, err
|
||||
}
|
||||
defer q.unlock()
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestQueue(t *testing.T) {
|
||||
cancel()
|
||||
|
||||
q := newQueue[int]()
|
||||
if got, err := q.get(nonblocking, nil); err != context.Canceled {
|
||||
if got, err := q.get(nonblocking); err != context.Canceled {
|
||||
t.Fatalf("q.get() = %v, %v, want nil, context.Canceled", got, err)
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ func TestQueue(t *testing.T) {
|
||||
if !q.put(2) {
|
||||
t.Fatalf("q.put(2) = false, want true")
|
||||
}
|
||||
if got, err := q.get(nonblocking, nil); got != 1 || err != nil {
|
||||
if got, err := q.get(nonblocking); got != 1 || err != nil {
|
||||
t.Fatalf("q.get() = %v, %v, want 1, nil", got, err)
|
||||
}
|
||||
if got, err := q.get(nonblocking, nil); got != 2 || err != nil {
|
||||
if got, err := q.get(nonblocking); got != 2 || err != nil {
|
||||
t.Fatalf("q.get() = %v, %v, want 2, nil", got, err)
|
||||
}
|
||||
if got, err := q.get(nonblocking, nil); err != context.Canceled {
|
||||
if got, err := q.get(nonblocking); err != context.Canceled {
|
||||
t.Fatalf("q.get() = %v, %v, want nil, context.Canceled", got, err)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestQueue(t *testing.T) {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
q.put(3)
|
||||
}()
|
||||
if got, err := q.get(context.Background(), nil); got != 3 || err != nil {
|
||||
if got, err := q.get(context.Background()); got != 3 || err != nil {
|
||||
t.Fatalf("q.get() = %v, %v, want 3, nil", got, err)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestQueue(t *testing.T) {
|
||||
t.Fatalf("q.put(2) = false, want true")
|
||||
}
|
||||
q.close(io.EOF)
|
||||
if got, err := q.get(context.Background(), nil); got != 0 || err != io.EOF {
|
||||
if got, err := q.get(context.Background()); got != 0 || err != io.EOF {
|
||||
t.Fatalf("q.get() = %v, %v, want 0, io.EOF", got, err)
|
||||
}
|
||||
if q.put(5) {
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func testSides(t *testing.T, name string, f func(*testing.T, connSide)) {
|
||||
@@ -16,6 +19,16 @@ func testSides(t *testing.T, name string, f func(*testing.T, connSide)) {
|
||||
t.Run(name+"client", func(t *testing.T) { f(t, clientSide) })
|
||||
}
|
||||
|
||||
func testSidesSynctest(t *testing.T, name string, f func(*testing.T, connSide)) {
|
||||
t.Helper()
|
||||
testSides(t, name, func(t *testing.T, side connSide) {
|
||||
t.Helper()
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
f(t, side)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testStreamTypes(t *testing.T, name string, f func(*testing.T, streamType)) {
|
||||
if name != "" {
|
||||
name += "/"
|
||||
@@ -24,6 +37,16 @@ func testStreamTypes(t *testing.T, name string, f func(*testing.T, streamType))
|
||||
t.Run(name+"uni", func(t *testing.T) { f(t, uniStream) })
|
||||
}
|
||||
|
||||
func testStreamTypesSynctest(t *testing.T, name string, f func(*testing.T, streamType)) {
|
||||
t.Helper()
|
||||
testStreamTypes(t, name, func(t *testing.T, stype streamType) {
|
||||
t.Helper()
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
f(t, stype)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testSidesAndStreamTypes(t *testing.T, name string, f func(*testing.T, connSide, streamType)) {
|
||||
if name != "" {
|
||||
name += "/"
|
||||
@@ -33,3 +56,20 @@ func testSidesAndStreamTypes(t *testing.T, name string, f func(*testing.T, connS
|
||||
t.Run(name+"server/uni", func(t *testing.T) { f(t, serverSide, uniStream) })
|
||||
t.Run(name+"client/uni", func(t *testing.T) { f(t, clientSide, uniStream) })
|
||||
}
|
||||
|
||||
func testSidesAndStreamTypesSynctest(t *testing.T, name string, f func(*testing.T, connSide, streamType)) {
|
||||
t.Helper()
|
||||
testSidesAndStreamTypes(t, name, func(t *testing.T, side connSide, stype streamType) {
|
||||
t.Helper()
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
f(t, side, stype)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func synctestSubtest(t *testing.T, name string, f func(t *testing.T)) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
synctest.Test(t, f)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -10,6 +12,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -77,9 +80,12 @@ func newRetryServerTest(t *testing.T) *retryServerTest {
|
||||
}
|
||||
|
||||
func TestRetryServerSucceeds(t *testing.T) {
|
||||
synctest.Test(t, testRetryServerSucceeds)
|
||||
}
|
||||
func testRetryServerSucceeds(t *testing.T) {
|
||||
rt := newRetryServerTest(t)
|
||||
te := rt.te
|
||||
te.advance(retryTokenValidityPeriod)
|
||||
time.Sleep(retryTokenValidityPeriod)
|
||||
te.writeDatagram(&testDatagram{
|
||||
packets: []*testPacket{{
|
||||
ptype: packetTypeInitial,
|
||||
@@ -117,6 +123,9 @@ func TestRetryServerSucceeds(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryServerTokenInvalid(t *testing.T) {
|
||||
synctest.Test(t, testRetryServerTokenInvalid)
|
||||
}
|
||||
func testRetryServerTokenInvalid(t *testing.T) {
|
||||
// "If a server receives a client Initial that contains an invalid Retry token [...]
|
||||
// the server SHOULD immediately close [...] the connection with an
|
||||
// INVALID_TOKEN error."
|
||||
@@ -147,11 +156,14 @@ func TestRetryServerTokenInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryServerTokenTooOld(t *testing.T) {
|
||||
synctest.Test(t, testRetryServerTokenTooOld)
|
||||
}
|
||||
func testRetryServerTokenTooOld(t *testing.T) {
|
||||
// "[...] a token SHOULD have an expiration time [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.3-3
|
||||
rt := newRetryServerTest(t)
|
||||
te := rt.te
|
||||
te.advance(retryTokenValidityPeriod + time.Second)
|
||||
time.Sleep(retryTokenValidityPeriod + time.Second)
|
||||
te.writeDatagram(&testDatagram{
|
||||
packets: []*testPacket{{
|
||||
ptype: packetTypeInitial,
|
||||
@@ -176,6 +188,9 @@ func TestRetryServerTokenTooOld(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryServerTokenWrongIP(t *testing.T) {
|
||||
synctest.Test(t, testRetryServerTokenWrongIP)
|
||||
}
|
||||
func testRetryServerTokenWrongIP(t *testing.T) {
|
||||
// "Tokens sent in Retry packets SHOULD include information that allows the server
|
||||
// to verify that the source IP address and port in client packets remain constant."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4-3
|
||||
@@ -206,6 +221,9 @@ func TestRetryServerTokenWrongIP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryServerIgnoresRetry(t *testing.T) {
|
||||
synctest.Test(t, testRetryServerIgnoresRetry)
|
||||
}
|
||||
func testRetryServerIgnoresRetry(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
tc.write(&testDatagram{
|
||||
@@ -225,6 +243,9 @@ func TestRetryServerIgnoresRetry(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryClientSuccess(t *testing.T) {
|
||||
synctest.Test(t, testRetryClientSuccess)
|
||||
}
|
||||
func testRetryClientSuccess(t *testing.T) {
|
||||
// "This token MUST be repeated by the client in all Initial packets it sends
|
||||
// for that connection after it receives the Retry packet."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.2-1
|
||||
@@ -323,7 +344,7 @@ func TestRetryClientInvalidServerTransportParameters(t *testing.T) {
|
||||
p.retrySrcConnID = []byte("invalid")
|
||||
},
|
||||
}} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
synctestSubtest(t, test.name, func(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide,
|
||||
func(p *transportParameters) {
|
||||
p.initialSrcConnID = initialSrcConnID
|
||||
@@ -367,6 +388,9 @@ func TestRetryClientInvalidServerTransportParameters(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
|
||||
synctest.Test(t, testRetryClientIgnoresRetryAfterReceivingPacket)
|
||||
}
|
||||
func testRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
|
||||
// "After the client has received and processed an Initial or Retry packet
|
||||
// from the server, it MUST discard any subsequent Retry packets that it receives."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1
|
||||
@@ -401,6 +425,9 @@ func TestRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
|
||||
synctest.Test(t, testRetryClientIgnoresRetryAfterReceivingRetry)
|
||||
}
|
||||
func testRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
|
||||
// "After the client has received and processed an Initial or Retry packet
|
||||
// from the server, it MUST discard any subsequent Retry packets that it receives."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1
|
||||
@@ -424,6 +451,9 @@ func TestRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
|
||||
synctest.Test(t, testRetryClientIgnoresRetryWithInvalidIntegrityTag)
|
||||
}
|
||||
func testRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.wantFrameType("client Initial CRYPTO data",
|
||||
packetTypeInitial, debugFrameCrypto{})
|
||||
@@ -441,6 +471,9 @@ func TestRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) {
|
||||
synctest.Test(t, testRetryClientIgnoresRetryWithZeroLengthToken)
|
||||
}
|
||||
func testRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) {
|
||||
// "A client MUST discard a Retry packet with a zero-length Retry Token field."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-2
|
||||
tc := newTestConn(t, clientSide)
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestSkipPackets(t *testing.T) {
|
||||
synctest.Test(t, testSkipPackets)
|
||||
}
|
||||
func testSkipPackets(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
connWritesPacket := func() {
|
||||
s.WriteByte(0)
|
||||
@@ -39,6 +47,9 @@ expectSkip:
|
||||
}
|
||||
|
||||
func TestSkipAckForSkippedPacket(t *testing.T) {
|
||||
synctest.Test(t, testSkipAckForSkippedPacket)
|
||||
}
|
||||
func testSkipAckForSkippedPacket(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
|
||||
// Cause the connection to send packets until it skips a packet number.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -12,10 +14,14 @@ import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
|
||||
synctest.Test(t, testStatelessResetClientSendsStatelessResetTokenTransportParameter)
|
||||
}
|
||||
func testStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
|
||||
// "[The stateless_reset_token] transport parameter MUST NOT be sent by a client [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.6.1
|
||||
resetToken := testPeerStatelessResetToken(0)
|
||||
@@ -61,6 +67,9 @@ func newDatagramForReset(cid []byte, size int, addr netip.AddrPort) *datagram {
|
||||
}
|
||||
|
||||
func TestStatelessResetSentSizes(t *testing.T) {
|
||||
synctest.Test(t, testStatelessResetSentSizes)
|
||||
}
|
||||
func testStatelessResetSentSizes(t *testing.T) {
|
||||
config := &Config{
|
||||
TLSConfig: newTestTLSConfig(serverSide),
|
||||
StatelessResetKey: testStatelessResetKey,
|
||||
@@ -126,6 +135,9 @@ func TestStatelessResetSentSizes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
|
||||
synctest.Test(t, testStatelessResetSuccessfulNewConnectionID)
|
||||
}
|
||||
func testStatelessResetSuccessfulNewConnectionID(t *testing.T) {
|
||||
// "[...] Stateless Reset Token field values from [...] NEW_CONNECTION_ID frames [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
|
||||
qr := &qlogRecord{}
|
||||
@@ -155,7 +167,7 @@ func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
|
||||
t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
|
||||
}
|
||||
tc.wantIdle("closed connection is idle in draining")
|
||||
tc.advance(1 * time.Second) // long enough to exit the draining state
|
||||
time.Sleep(1 * time.Second) // long enough to exit the draining state
|
||||
tc.wantIdle("closed connection is idle after draining")
|
||||
|
||||
qr.wantEvents(t, jsonEvent{
|
||||
@@ -167,6 +179,9 @@ func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStatelessResetSuccessfulTransportParameter(t *testing.T) {
|
||||
synctest.Test(t, testStatelessResetSuccessfulTransportParameter)
|
||||
}
|
||||
func testStatelessResetSuccessfulTransportParameter(t *testing.T) {
|
||||
// "[...] Stateless Reset Token field values from [...]
|
||||
// the server's transport parameters [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
|
||||
@@ -229,7 +244,7 @@ func TestStatelessResetSuccessfulPrefix(t *testing.T) {
|
||||
}, testLocalConnID(0)...),
|
||||
size: 100,
|
||||
}} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
synctestSubtest(t, test.name, func(t *testing.T) {
|
||||
resetToken := testPeerStatelessResetToken(0)
|
||||
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
||||
p.statelessResetToken = resetToken[:]
|
||||
@@ -252,6 +267,9 @@ func TestStatelessResetSuccessfulPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStatelessResetRetiredConnID(t *testing.T) {
|
||||
synctest.Test(t, testStatelessResetRetiredConnID)
|
||||
}
|
||||
func testStatelessResetRetiredConnID(t *testing.T) {
|
||||
// "An endpoint MUST NOT check for any stateless reset tokens [...]
|
||||
// for connection IDs that have been retired."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-3
|
||||
|
||||
@@ -236,7 +236,7 @@ func (s *Stream) Read(b []byte) (n int, err error) {
|
||||
s.inbufoff += n
|
||||
return n, nil
|
||||
}
|
||||
if err := s.ingate.waitAndLock(s.inctx, s.conn.testHooks); err != nil {
|
||||
if err := s.ingate.waitAndLock(s.inctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if s.inbufoff > 0 {
|
||||
@@ -350,7 +350,7 @@ func (s *Stream) Write(b []byte) (n int, err error) {
|
||||
if len(b) > 0 && !canWrite {
|
||||
// Our send buffer is full. Wait for the peer to ack some data.
|
||||
s.outUnlock()
|
||||
if err := s.outgate.waitAndLock(s.outctx, s.conn.testHooks); err != nil {
|
||||
if err := s.outgate.waitAndLock(s.outctx); err != nil {
|
||||
return n, err
|
||||
}
|
||||
// Successfully returning from waitAndLockGate means we are no longer
|
||||
|
||||
@@ -29,7 +29,7 @@ func (lim *localStreamLimits) init() {
|
||||
// open creates a new local stream, blocking until MAX_STREAMS quota is available.
|
||||
func (lim *localStreamLimits) open(ctx context.Context, c *Conn) (num int64, err error) {
|
||||
// TODO: Send a STREAMS_BLOCKED when blocked.
|
||||
if err := lim.gate.waitAndLock(ctx, c.testHooks); err != nil {
|
||||
if err := lim.gate.waitAndLock(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if lim.opened < 0 {
|
||||
|
||||
@@ -2,19 +2,22 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestStreamLimitNewStreamBlocked(t *testing.T) {
|
||||
// "An endpoint that receives a frame with a stream ID exceeding the limit
|
||||
// it has sent MUST treat this as a connection error of type STREAM_LIMIT_ERROR [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-3
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, clientSide,
|
||||
permissiveTransportParameters,
|
||||
@@ -46,7 +49,7 @@ func TestStreamLimitNewStreamBlocked(t *testing.T) {
|
||||
func TestStreamLimitMaxStreamsDecreases(t *testing.T) {
|
||||
// "MAX_STREAMS frames that do not increase the stream limit MUST be ignored."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-4
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, clientSide,
|
||||
permissiveTransportParameters,
|
||||
@@ -77,7 +80,7 @@ func TestStreamLimitMaxStreamsDecreases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamLimitViolated(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide,
|
||||
func(c *Config) {
|
||||
if styp == bidiStream {
|
||||
@@ -104,7 +107,7 @@ func TestStreamLimitViolated(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamLimitImplicitStreams(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide,
|
||||
func(c *Config) {
|
||||
c.MaxBidiRemoteStreams = 1 << 60
|
||||
@@ -152,7 +155,7 @@ func TestStreamLimitMaxStreamsTransportParameterTooLarge(t *testing.T) {
|
||||
// a value greater than 2^60 [...] the connection MUST be closed
|
||||
// immediately with a connection error of type TRANSPORT_PARAMETER_ERROR [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide,
|
||||
func(p *transportParameters) {
|
||||
if styp == bidiStream {
|
||||
@@ -177,7 +180,7 @@ func TestStreamLimitMaxStreamsFrameTooLarge(t *testing.T) {
|
||||
// greater than 2^60 [...] the connection MUST be closed immediately
|
||||
// with a connection error [...] of type FRAME_ENCODING_ERROR [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
tc.writeFrames(packetTypeInitial,
|
||||
@@ -197,7 +200,7 @@ func TestStreamLimitMaxStreamsFrameTooLarge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamLimitSendUpdatesMaxStreams(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
if styp == uniStream {
|
||||
c.MaxUniRemoteStreams = 4
|
||||
@@ -236,6 +239,9 @@ func TestStreamLimitSendUpdatesMaxStreams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamLimitStopSendingDoesNotUpdateMaxStreams(t *testing.T) {
|
||||
synctest.Test(t, testStreamLimitStopSendingDoesNotUpdateMaxStreams)
|
||||
}
|
||||
func testStreamLimitStopSendingDoesNotUpdateMaxStreams(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, func(c *Config) {
|
||||
c.MaxBidiRemoteStreams = 1
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -13,12 +15,13 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
|
||||
"golang.org/x/net/internal/quic/quicwire"
|
||||
)
|
||||
|
||||
func TestStreamWriteBlockedByOutputBuffer(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
const writeBufferSize = 4
|
||||
tc := newTestConn(t, clientSide, permissiveTransportParameters, func(c *Config) {
|
||||
@@ -79,7 +82,7 @@ func TestStreamWriteBlockedByOutputBuffer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamWriteBlockedByStreamFlowControl(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
ctx := canceledContext()
|
||||
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
||||
@@ -149,7 +152,7 @@ func TestStreamIgnoresMaxStreamDataReduction(t *testing.T) {
|
||||
// "A sender MUST ignore any MAX_STREAM_DATA [...] frames that
|
||||
// do not increase flow control limits."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.1-9
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
ctx := canceledContext()
|
||||
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
||||
@@ -218,7 +221,7 @@ func TestStreamIgnoresMaxStreamDataReduction(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamWriteBlockedByWriteBufferLimit(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
const maxWriteBuffer = 4
|
||||
tc := newTestConn(t, clientSide, func(p *transportParameters) {
|
||||
@@ -392,7 +395,7 @@ func TestStreamReceive(t *testing.T) {
|
||||
wantEOF: true,
|
||||
}},
|
||||
}} {
|
||||
testStreamTypes(t, test.name, func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, test.name, func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
sid := newStreamID(clientSide, styp, 0)
|
||||
@@ -439,7 +442,7 @@ func TestStreamReceive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamReceiveExtendsStreamWindow(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
const maxWindowSize = 20
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
@@ -484,7 +487,7 @@ func TestStreamReceiveViolatesStreamDataLimit(t *testing.T) {
|
||||
// "A receiver MUST close the connection with an error of type FLOW_CONTROL_ERROR if
|
||||
// the sender violates the advertised [...] stream data limits [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.1-8
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
const maxStreamData = 10
|
||||
for _, test := range []struct {
|
||||
off int64
|
||||
@@ -521,7 +524,7 @@ func TestStreamReceiveViolatesStreamDataLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamReceiveDuplicateDataDoesNotViolateLimits(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
const maxData = 10
|
||||
tc := newTestConn(t, serverSide, func(c *Config) {
|
||||
// TODO: Add connection-level maximum data here as well.
|
||||
@@ -544,7 +547,7 @@ func TestStreamReceiveEmptyEOF(t *testing.T) {
|
||||
// A stream receives some data, we read a byte of that data
|
||||
// (causing the rest to be pulled into the s.inbuf buffer),
|
||||
// and then we receive a FIN with no additional data.
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, styp, permissiveTransportParameters)
|
||||
want := []byte{1, 2, 3}
|
||||
tc.writeFrames(packetType1RTT, debugFrameStream{
|
||||
@@ -568,7 +571,7 @@ func TestStreamReceiveEmptyEOF(t *testing.T) {
|
||||
|
||||
func TestStreamReadByteFromOneByteStream(t *testing.T) {
|
||||
// ReadByte on the only byte of a stream should not return an error.
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, styp, permissiveTransportParameters)
|
||||
want := byte(1)
|
||||
tc.writeFrames(packetType1RTT, debugFrameStream{
|
||||
@@ -608,7 +611,7 @@ func finalSizeTest(t *testing.T, wantErr transportError, f func(tc *testConn, si
|
||||
})
|
||||
},
|
||||
}} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
synctestSubtest(t, test.name, func(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, opts...)
|
||||
tc.handshake()
|
||||
sid := newStreamID(clientSide, styp, 0)
|
||||
@@ -662,7 +665,7 @@ func TestStreamDataBeyondFinalSize(t *testing.T) {
|
||||
// "A receiver SHOULD treat receipt of data at or beyond
|
||||
// the final size as an error of type FINAL_SIZE_ERROR [...]"
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-4.5-5
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
sid := newStreamID(clientSide, styp, 0)
|
||||
@@ -688,7 +691,7 @@ func TestStreamDataBeyondFinalSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamReceiveUnblocksReader(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
@@ -746,7 +749,7 @@ func TestStreamReceiveUnblocksReader(t *testing.T) {
|
||||
// It then sends the returned frame (STREAM, STREAM_DATA_BLOCKED, etc.)
|
||||
// to the conn and expects a STREAM_STATE_ERROR.
|
||||
func testStreamSendFrameInvalidState(t *testing.T, f func(sid streamID) debugFrame) {
|
||||
testSides(t, "stream_not_created", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "stream_not_created", func(t *testing.T, side connSide) {
|
||||
tc := newTestConn(t, side, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
tc.writeFrames(packetType1RTT, f(newStreamID(side, bidiStream, 0)))
|
||||
@@ -755,7 +758,7 @@ func testStreamSendFrameInvalidState(t *testing.T, f func(sid streamID) debugFra
|
||||
code: errStreamState,
|
||||
})
|
||||
})
|
||||
testSides(t, "uni_stream", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "uni_stream", func(t *testing.T, side connSide) {
|
||||
ctx := canceledContext()
|
||||
tc := newTestConn(t, side, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
@@ -823,7 +826,7 @@ func TestStreamDataBlockedInvalidState(t *testing.T) {
|
||||
// It then sends the returned frame (MAX_STREAM_DATA, STOP_SENDING, etc.)
|
||||
// to the conn and expects a STREAM_STATE_ERROR.
|
||||
func testStreamReceiveFrameInvalidState(t *testing.T, f func(sid streamID) debugFrame) {
|
||||
testSides(t, "stream_not_created", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "stream_not_created", func(t *testing.T, side connSide) {
|
||||
tc := newTestConn(t, side)
|
||||
tc.handshake()
|
||||
tc.writeFrames(packetType1RTT, f(newStreamID(side, bidiStream, 0)))
|
||||
@@ -832,7 +835,7 @@ func testStreamReceiveFrameInvalidState(t *testing.T, f func(sid streamID) debug
|
||||
code: errStreamState,
|
||||
})
|
||||
})
|
||||
testSides(t, "uni_stream", func(t *testing.T, side connSide) {
|
||||
testSidesSynctest(t, "uni_stream", func(t *testing.T, side connSide) {
|
||||
tc := newTestConn(t, side)
|
||||
tc.handshake()
|
||||
tc.writeFrames(packetType1RTT, f(newStreamID(side.peer(), uniStream, 0)))
|
||||
@@ -873,6 +876,9 @@ func TestStreamMaxStreamDataInvalidState(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamOffsetTooLarge(t *testing.T) {
|
||||
synctest.Test(t, testStreamOffsetTooLarge)
|
||||
}
|
||||
func testStreamOffsetTooLarge(t *testing.T) {
|
||||
// "Receipt of a frame that exceeds [2^62-1] MUST be treated as a
|
||||
// connection error of type FRAME_ENCODING_ERROR or FLOW_CONTROL_ERROR."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-9
|
||||
@@ -894,6 +900,9 @@ func TestStreamOffsetTooLarge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamReadFromWriteOnlyStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamReadFromWriteOnlyStream)
|
||||
}
|
||||
func testStreamReadFromWriteOnlyStream(t *testing.T) {
|
||||
_, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
buf := make([]byte, 10)
|
||||
wantErr := "read from write-only stream"
|
||||
@@ -903,6 +912,9 @@ func TestStreamReadFromWriteOnlyStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamWriteToReadOnlyStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamWriteToReadOnlyStream)
|
||||
}
|
||||
func testStreamWriteToReadOnlyStream(t *testing.T) {
|
||||
_, s := newTestConnAndRemoteStream(t, serverSide, uniStream)
|
||||
buf := make([]byte, 10)
|
||||
wantErr := "write to read-only stream"
|
||||
@@ -912,6 +924,9 @@ func TestStreamWriteToReadOnlyStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamReadFromClosedStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamReadFromClosedStream)
|
||||
}
|
||||
func testStreamReadFromClosedStream(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, permissiveTransportParameters)
|
||||
s.CloseRead()
|
||||
tc.wantFrame("CloseRead sends a STOP_SENDING frame",
|
||||
@@ -934,6 +949,9 @@ func TestStreamReadFromClosedStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamCloseReadWithAllDataReceived(t *testing.T) {
|
||||
synctest.Test(t, testStreamCloseReadWithAllDataReceived)
|
||||
}
|
||||
func testStreamCloseReadWithAllDataReceived(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, permissiveTransportParameters)
|
||||
tc.writeFrames(packetType1RTT, debugFrameStream{
|
||||
id: s.id,
|
||||
@@ -950,6 +968,9 @@ func TestStreamCloseReadWithAllDataReceived(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamWriteToClosedStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamWriteToClosedStream)
|
||||
}
|
||||
func testStreamWriteToClosedStream(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, bidiStream, permissiveTransportParameters)
|
||||
s.CloseWrite()
|
||||
tc.wantFrame("stream is opened after being closed",
|
||||
@@ -966,6 +987,9 @@ func TestStreamWriteToClosedStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamResetBlockedStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamResetBlockedStream)
|
||||
}
|
||||
func testStreamResetBlockedStream(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, bidiStream, permissiveTransportParameters,
|
||||
func(c *Config) {
|
||||
c.MaxStreamWriteBufferSize = 4
|
||||
@@ -1002,6 +1026,9 @@ func TestStreamResetBlockedStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamWriteMoreThanOnePacketOfData(t *testing.T) {
|
||||
synctest.Test(t, testStreamWriteMoreThanOnePacketOfData)
|
||||
}
|
||||
func testStreamWriteMoreThanOnePacketOfData(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
|
||||
p.initialMaxStreamsUni = 1
|
||||
p.initialMaxData = 1 << 20
|
||||
@@ -1038,6 +1065,9 @@ func TestStreamWriteMoreThanOnePacketOfData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamCloseWaitsForAcks(t *testing.T) {
|
||||
synctest.Test(t, testStreamCloseWaitsForAcks)
|
||||
}
|
||||
func testStreamCloseWaitsForAcks(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
data := make([]byte, 100)
|
||||
s.Write(data)
|
||||
@@ -1071,6 +1101,9 @@ func TestStreamCloseWaitsForAcks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamCloseReadOnly(t *testing.T) {
|
||||
synctest.Test(t, testStreamCloseReadOnly)
|
||||
}
|
||||
func testStreamCloseReadOnly(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
if err := s.Close(); err != nil {
|
||||
t.Errorf("s.Close() = %v, want nil", err)
|
||||
@@ -1103,10 +1136,10 @@ func TestStreamCloseUnblocked(t *testing.T) {
|
||||
name: "stream reset",
|
||||
unblock: func(tc *testConn, s *Stream) {
|
||||
s.Reset(0)
|
||||
tc.wait() // wait for test conn to process the Reset
|
||||
synctest.Wait() // wait for test conn to process the Reset
|
||||
},
|
||||
}} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
synctestSubtest(t, test.name, func(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
data := make([]byte, 100)
|
||||
s.Write(data)
|
||||
@@ -1148,6 +1181,9 @@ func TestStreamCloseUnblocked(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) {
|
||||
synctest.Test(t, testStreamCloseWriteWhenBlockedByStreamFlowControl)
|
||||
}
|
||||
func testStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters,
|
||||
func(p *transportParameters) {
|
||||
//p.initialMaxData = 0
|
||||
@@ -1185,7 +1221,7 @@ func TestStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamPeerResetsWithUnreadAndUnsentData(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, styp)
|
||||
data := []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
||||
tc.writeFrames(packetType1RTT, debugFrameStream{
|
||||
@@ -1210,7 +1246,7 @@ func TestStreamPeerResetsWithUnreadAndUnsentData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamPeerResetWakesBlockedRead(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, styp)
|
||||
reader := runAsync(tc, func(ctx context.Context) (int, error) {
|
||||
s.SetReadContext(ctx)
|
||||
@@ -1231,7 +1267,7 @@ func TestStreamPeerResetWakesBlockedRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamPeerResetFollowedByData(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, styp)
|
||||
tc.writeFrames(packetType1RTT, debugFrameResetStream{
|
||||
id: s.id,
|
||||
@@ -1256,6 +1292,9 @@ func TestStreamPeerResetFollowedByData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamResetInvalidCode(t *testing.T) {
|
||||
synctest.Test(t, testStreamResetInvalidCode)
|
||||
}
|
||||
func testStreamResetInvalidCode(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
|
||||
s.Reset(1 << 62)
|
||||
tc.wantFrame("reset with invalid code sends a RESET_STREAM anyway",
|
||||
@@ -1268,6 +1307,9 @@ func TestStreamResetInvalidCode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamResetReceiveOnly(t *testing.T) {
|
||||
synctest.Test(t, testStreamResetReceiveOnly)
|
||||
}
|
||||
func testStreamResetReceiveOnly(t *testing.T) {
|
||||
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream)
|
||||
s.Reset(0)
|
||||
tc.wantIdle("resetting a receive-only stream has no effect")
|
||||
@@ -1277,7 +1319,7 @@ func TestStreamPeerStopSendingForActiveStream(t *testing.T) {
|
||||
// "An endpoint that receives a STOP_SENDING frame MUST send a RESET_STREAM frame if
|
||||
// the stream is in the "Ready" or "Send" state."
|
||||
// https://www.rfc-editor.org/rfc/rfc9000#section-3.5-4
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndLocalStream(t, serverSide, styp, permissiveTransportParameters)
|
||||
for i := 0; i < 4; i++ {
|
||||
s.Write([]byte{byte(i)})
|
||||
@@ -1309,6 +1351,9 @@ func TestStreamPeerStopSendingForActiveStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamReceiveDataBlocked(t *testing.T) {
|
||||
synctest.Test(t, testStreamReceiveDataBlocked)
|
||||
}
|
||||
func testStreamReceiveDataBlocked(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, permissiveTransportParameters)
|
||||
tc.handshake()
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
@@ -1326,7 +1371,7 @@ func TestStreamReceiveDataBlocked(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamFlushExplicit(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, styp, permissiveTransportParameters)
|
||||
want := []byte{0, 1, 2, 3}
|
||||
n, err := s.Write(want)
|
||||
@@ -1344,6 +1389,9 @@ func TestStreamFlushExplicit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamFlushClosedStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamFlushClosedStream)
|
||||
}
|
||||
func testStreamFlushClosedStream(t *testing.T) {
|
||||
_, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
|
||||
permissiveTransportParameters)
|
||||
s.Close()
|
||||
@@ -1353,6 +1401,9 @@ func TestStreamFlushClosedStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamFlushResetStream(t *testing.T) {
|
||||
synctest.Test(t, testStreamFlushResetStream)
|
||||
}
|
||||
func testStreamFlushResetStream(t *testing.T) {
|
||||
_, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
|
||||
permissiveTransportParameters)
|
||||
s.Reset(0)
|
||||
@@ -1362,6 +1413,9 @@ func TestStreamFlushResetStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamFlushStreamAfterPeerStopSending(t *testing.T) {
|
||||
synctest.Test(t, testStreamFlushStreamAfterPeerStopSending)
|
||||
}
|
||||
func testStreamFlushStreamAfterPeerStopSending(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
|
||||
permissiveTransportParameters)
|
||||
s.Flush() // create the stream
|
||||
@@ -1381,6 +1435,9 @@ func TestStreamFlushStreamAfterPeerStopSending(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamErrorsAfterConnectionClosed(t *testing.T) {
|
||||
synctest.Test(t, testStreamErrorsAfterConnectionClosed)
|
||||
}
|
||||
func testStreamErrorsAfterConnectionClosed(t *testing.T) {
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
|
||||
permissiveTransportParameters)
|
||||
wantErr := &ApplicationError{Code: 42}
|
||||
@@ -1399,7 +1456,7 @@ func TestStreamErrorsAfterConnectionClosed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamFlushImplicitExact(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
const writeBufferSize = 4
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, styp,
|
||||
permissiveTransportParameters,
|
||||
@@ -1429,7 +1486,7 @@ func TestStreamFlushImplicitExact(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStreamFlushImplicitLargerThanBuffer(t *testing.T) {
|
||||
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
|
||||
testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
|
||||
const writeBufferSize = 4
|
||||
tc, s := newTestConnAndLocalStream(t, clientSide, styp,
|
||||
permissiveTransportParameters,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -9,12 +11,12 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
// handshake executes the handshake.
|
||||
func (tc *testConn) handshake() {
|
||||
tc.t.Helper()
|
||||
if *testVV {
|
||||
*testVV = false
|
||||
defer func() {
|
||||
@@ -32,16 +34,16 @@ func (tc *testConn) handshake() {
|
||||
i := 0
|
||||
for {
|
||||
if i == len(dgrams)-1 {
|
||||
want := tc.endpoint.now.Add(maxAckDelay - timerGranularity)
|
||||
want := time.Now().Add(maxAckDelay - timerGranularity)
|
||||
if tc.conn.side == clientSide {
|
||||
if !tc.timer.Equal(want) {
|
||||
t.Fatalf("want timer = %v (max_ack_delay), got %v", want, tc.timer)
|
||||
if got := tc.nextEvent(); !got.Equal(want) {
|
||||
t.Fatalf("want timer = %v (max_ack_delay), got %v", want, got)
|
||||
}
|
||||
if got := tc.readDatagram(); got != nil {
|
||||
t.Fatalf("client unexpectedly sent: %v", got)
|
||||
}
|
||||
}
|
||||
tc.advanceTo(want)
|
||||
time.Sleep(time.Until(want))
|
||||
}
|
||||
|
||||
// Check that we're sending exactly the data we expect.
|
||||
@@ -308,20 +310,29 @@ func (tc *testConn) uncheckedHandshake() {
|
||||
}
|
||||
|
||||
func TestConnClientHandshake(t *testing.T) {
|
||||
synctest.Test(t, testConnClientHandshake)
|
||||
}
|
||||
func testConnClientHandshake(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.handshake()
|
||||
tc.advance(1 * time.Second)
|
||||
time.Sleep(1 * time.Second)
|
||||
tc.wantIdle("no packets should be sent by an idle conn after the handshake")
|
||||
}
|
||||
|
||||
func TestConnServerHandshake(t *testing.T) {
|
||||
synctest.Test(t, testConnServerHandshake)
|
||||
}
|
||||
func testConnServerHandshake(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
tc.advance(1 * time.Second)
|
||||
time.Sleep(1 * time.Second)
|
||||
tc.wantIdle("no packets should be sent by an idle conn after the handshake")
|
||||
}
|
||||
|
||||
func TestConnKeysDiscardedClient(t *testing.T) {
|
||||
synctest.Test(t, testConnKeysDiscardedClient)
|
||||
}
|
||||
func testConnKeysDiscardedClient(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -370,6 +381,9 @@ func TestConnKeysDiscardedClient(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnKeysDiscardedServer(t *testing.T) {
|
||||
synctest.Test(t, testConnKeysDiscardedServer)
|
||||
}
|
||||
func testConnKeysDiscardedServer(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -425,6 +439,9 @@ func TestConnKeysDiscardedServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInvalidCryptoData(t *testing.T) {
|
||||
synctest.Test(t, testConnInvalidCryptoData)
|
||||
}
|
||||
func testConnInvalidCryptoData(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -455,6 +472,9 @@ func TestConnInvalidCryptoData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnInvalidPeerCertificate(t *testing.T) {
|
||||
synctest.Test(t, testConnInvalidPeerCertificate)
|
||||
}
|
||||
func testConnInvalidPeerCertificate(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide, func(c *tls.Config) {
|
||||
c.VerifyPeerCertificate = func([][]byte, [][]*x509.Certificate) error {
|
||||
return errors.New("I will not buy this certificate. It is scratched.")
|
||||
@@ -481,6 +501,9 @@ func TestConnInvalidPeerCertificate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnHandshakeDoneSentToServer(t *testing.T) {
|
||||
synctest.Test(t, testConnHandshakeDoneSentToServer)
|
||||
}
|
||||
func testConnHandshakeDoneSentToServer(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide)
|
||||
tc.handshake()
|
||||
|
||||
@@ -493,6 +516,9 @@ func TestConnHandshakeDoneSentToServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCryptoDataOutOfOrder(t *testing.T) {
|
||||
synctest.Test(t, testConnCryptoDataOutOfOrder)
|
||||
}
|
||||
func testConnCryptoDataOutOfOrder(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -531,6 +557,9 @@ func TestConnCryptoDataOutOfOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnCryptoBufferSizeExceeded(t *testing.T) {
|
||||
synctest.Test(t, testConnCryptoBufferSizeExceeded)
|
||||
}
|
||||
func testConnCryptoBufferSizeExceeded(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
|
||||
@@ -550,6 +579,9 @@ func TestConnCryptoBufferSizeExceeded(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnAEADLimitReached(t *testing.T) {
|
||||
synctest.Test(t, testConnAEADLimitReached)
|
||||
}
|
||||
func testConnAEADLimitReached(t *testing.T) {
|
||||
// "[...] endpoints MUST count the number of received packets that
|
||||
// fail authentication during the lifetime of a connection.
|
||||
// If the total number of received packets that fail authentication [...]
|
||||
@@ -590,7 +622,7 @@ func TestConnAEADLimitReached(t *testing.T) {
|
||||
tc.conn.sendMsg(&datagram{
|
||||
b: invalid,
|
||||
})
|
||||
tc.wait()
|
||||
synctest.Wait()
|
||||
}
|
||||
|
||||
// Set the conn's auth failure count to just before the AEAD integrity limit.
|
||||
@@ -610,11 +642,14 @@ func TestConnAEADLimitReached(t *testing.T) {
|
||||
})
|
||||
|
||||
tc.writeFrames(packetType1RTT, debugFramePing{})
|
||||
tc.advance(1 * time.Second)
|
||||
time.Sleep(1 * time.Second)
|
||||
tc.wantIdle("auth failures at limit: conn does not process additional packets")
|
||||
}
|
||||
|
||||
func TestConnKeysDiscardedWithExcessCryptoData(t *testing.T) {
|
||||
synctest.Test(t, testConnKeysDiscardedWithExcessCryptoData)
|
||||
}
|
||||
func testConnKeysDiscardedWithExcessCryptoData(t *testing.T) {
|
||||
tc := newTestConn(t, serverSide, permissiveTransportParameters)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
tc.ignoreFrame(frameTypeNewConnectionID)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.25
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
@@ -9,9 +11,13 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func TestVersionNegotiationServerReceivesUnknownVersion(t *testing.T) {
|
||||
synctest.Test(t, testVersionNegotiationServerReceivesUnknownVersion)
|
||||
}
|
||||
func testVersionNegotiationServerReceivesUnknownVersion(t *testing.T) {
|
||||
config := &Config{
|
||||
TLSConfig: newTestTLSConfig(serverSide),
|
||||
}
|
||||
@@ -55,6 +61,9 @@ func TestVersionNegotiationServerReceivesUnknownVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersionNegotiationClientAborts(t *testing.T) {
|
||||
synctest.Test(t, testVersionNegotiationClientAborts)
|
||||
}
|
||||
func testVersionNegotiationClientAborts(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
p := tc.readPacket() // client Initial packet
|
||||
tc.endpoint.write(&datagram{
|
||||
@@ -67,6 +76,9 @@ func TestVersionNegotiationClientAborts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) {
|
||||
synctest.Test(t, testVersionNegotiationClientIgnoresAfterProcessingPacket)
|
||||
}
|
||||
func testVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
p := tc.readPacket() // client Initial packet
|
||||
@@ -89,6 +101,9 @@ func TestVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersionNegotiationClientIgnoresMismatchingSourceConnID(t *testing.T) {
|
||||
synctest.Test(t, testVersionNegotiationClientIgnoresMismatchingSourceConnID)
|
||||
}
|
||||
func testVersionNegotiationClientIgnoresMismatchingSourceConnID(t *testing.T) {
|
||||
tc := newTestConn(t, clientSide)
|
||||
tc.ignoreFrame(frameTypeAck)
|
||||
p := tc.readPacket() // client Initial packet
|
||||
|
||||
Reference in New Issue
Block a user