Source file src/net/http/internal/http3/transport_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http3
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/tls"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"maps"
    15  	"math"
    16  	"net"
    17  	"net/http"
    18  	"reflect"
    19  	"slices"
    20  	"testing"
    21  	"testing/synctest"
    22  	"time"
    23  
    24  	"golang.org/x/net/quic"
    25  )
    26  
    27  // unusablePacketConn is a net.PacketConn whose LocalAddr is not a valid
    28  // UDP address, which makes quic.NewEndpoint fail.
    29  type unusablePacketConn struct {
    30  	net.PacketConn
    31  	closed bool
    32  }
    33  
    34  func (c *unusablePacketConn) LocalAddr() net.Addr {
    35  	return &net.UnixAddr{Name: "unusable", Net: "unix"}
    36  }
    37  
    38  func (c *unusablePacketConn) Close() error {
    39  	c.closed = true
    40  	return nil
    41  }
    42  
    43  // TestTransportInitEndpointError verifies that a transport which fails to
    44  // create its QUIC endpoint reports the error, rather than proceeding with a
    45  // nil endpoint.
    46  func TestTransportInitEndpointError(t *testing.T) {
    47  	conn := &unusablePacketConn{}
    48  	tr := &transport{
    49  		tr1: new(http.Transport), // TLSClientConfig is nil, as by default
    50  		opts: TransportOpts{
    51  			ListenPacket: func(network, addr string) (net.PacketConn, error) {
    52  				return conn, nil
    53  			},
    54  		},
    55  		activeConns: make(map[*clientConn]struct{}),
    56  	}
    57  
    58  	if err := tr.initEndpoint(); err == nil {
    59  		t.Fatal("initEndpoint() = nil, want error")
    60  	}
    61  	if tr.endpoint != nil {
    62  		t.Errorf("after failed initEndpoint, transport.endpoint = %v, want nil", tr.endpoint)
    63  	}
    64  	if !conn.closed {
    65  		t.Errorf("after failed initEndpoint, the net.PacketConn was not closed")
    66  	}
    67  
    68  	// dial must report the error rather than panicking on the nil endpoint.
    69  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    70  	defer cancel()
    71  	if _, err := tr.dial(ctx, "127.0.0.1:443", &tls.Config{}, nil); err == nil {
    72  		t.Fatal("dial() = nil error, want error")
    73  	}
    74  }
    75  
    76  func TestTransportServerCreatesBidirectionalStream(t *testing.T) {
    77  	// "Clients MUST treat receipt of a server-initiated bidirectional
    78  	// stream as a connection error of type H3_STREAM_CREATION_ERROR [...]"
    79  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3
    80  	synctest.Test(t, func(t *testing.T) {
    81  		tc := newTestClientConn(t)
    82  		tc.greet()
    83  		st := tc.newStream(streamTypeRequest)
    84  		st.Flush()
    85  		tc.wantClosed("after server creates bidi stream", errH3StreamCreationError)
    86  	})
    87  }
    88  
    89  func TestClientConnMethods(t *testing.T) {
    90  	synctest.Test(t, func(t *testing.T) {
    91  		var called bool
    92  		hook := func() {
    93  			if called {
    94  				t.Error("state hook was unexpectedly called")
    95  			}
    96  			called = true
    97  		}
    98  		verifyHookWasCalled := func() {
    99  			if !called {
   100  				t.Error("state hook was unexpectedly not called")
   101  			}
   102  			called = false
   103  		}
   104  		tc := newTestClientConnWithHook(t, hook)
   105  		tc.greet()
   106  
   107  		// Initial state after establishing connection.
   108  		if err := tc.cc.Err(); err != nil {
   109  			t.Errorf("cc.Err() = %v, want nil", err)
   110  		}
   111  		if avail := tc.cc.Available(); avail != math.MaxInt {
   112  			t.Errorf("cc.Available() = %v, want %v", avail, math.MaxInt)
   113  		}
   114  		if inFlight := tc.cc.InFlight(); inFlight != 0 {
   115  			t.Errorf("cc.InFlight() = %v, want 0", inFlight)
   116  		}
   117  
   118  		// Release, with Reserve before and without.
   119  		for _, reserveBefore := range []bool{true, false} {
   120  			if reserveBefore {
   121  				if err := tc.cc.Reserve(); err != nil {
   122  					t.Fatalf("cc.Reserve() failed: %v", err)
   123  				}
   124  				if avail := tc.cc.Available(); avail != math.MaxInt {
   125  					t.Errorf("after Reserve, cc.Available() = %v, want %v", avail, math.MaxInt)
   126  				}
   127  				if inFlight := tc.cc.InFlight(); inFlight != 1 {
   128  					t.Errorf("after Reserve, cc.InFlight() = %v, want 1", inFlight)
   129  				}
   130  			}
   131  			tc.cc.Release()
   132  			if avail := tc.cc.Available(); avail != math.MaxInt {
   133  				t.Errorf("after Release, cc.Available() = %v, want %v", avail, math.MaxInt)
   134  			}
   135  			if inFlight := tc.cc.InFlight(); inFlight != 0 {
   136  				t.Errorf("after Release, cc.InFlight() = %v, want 0", inFlight)
   137  			}
   138  		}
   139  
   140  		// RoundTrip, with Reserve before and without.
   141  		for _, reserveBefore := range []bool{true, false} {
   142  			if reserveBefore {
   143  				if err := tc.cc.Reserve(); err != nil {
   144  					t.Fatalf("cc.Reserve() failed: %v", err)
   145  				}
   146  				if avail := tc.cc.Available(); avail != math.MaxInt {
   147  					t.Errorf("after Reserve, cc.Available() = %v, want %v", avail, math.MaxInt)
   148  				}
   149  				if inFlight := tc.cc.InFlight(); inFlight != 1 {
   150  					t.Errorf("after Reserve, cc.InFlight() = %v, want 1", inFlight)
   151  				}
   152  			}
   153  			req, _ := http.NewRequest("GET", "https://example.com/", nil)
   154  			rt := tc.roundTrip(req)
   155  			if avail := tc.cc.Available(); avail != math.MaxInt {
   156  				t.Errorf("after RoundTrip, cc.Available() = %v, want %v", avail, math.MaxInt)
   157  			}
   158  			st := tc.wantStream(streamTypeRequest)
   159  			st.wantHeaders(nil)
   160  			st.writeHeaders(http.Header{":status": []string{"200"}})
   161  			resp := rt.response()
   162  			if resp.StatusCode != 200 {
   163  				t.Errorf("resp.StatusCode = %v, want 200", resp.StatusCode)
   164  			}
   165  			if inFlight := tc.cc.InFlight(); inFlight != 1 { // InFlight should decrement only after the body is closed.
   166  				t.Errorf("before body close, cc.InFlight() = %v, want 1", inFlight)
   167  			}
   168  			resp.Body.Close()
   169  			if inFlight := tc.cc.InFlight(); inFlight != 0 {
   170  				t.Errorf("after body close, cc.InFlight() = %v, want 0", inFlight)
   171  			}
   172  			verifyHookWasCalled()
   173  		}
   174  
   175  		// Connection closure.
   176  		if err := tc.cc.Reserve(); err != nil {
   177  			t.Fatalf("cc.Reserve() failed: %v", err)
   178  		}
   179  		tc.cc.Close()
   180  		synctest.Wait()
   181  		verifyHookWasCalled()
   182  		if err := tc.cc.Err(); err == nil {
   183  			t.Error("after connection is closed, cc.Err() = nil, want err")
   184  		}
   185  		if avail := tc.cc.Available(); avail != 0 {
   186  			t.Errorf("after connection is closed, cc.Available() = %v, want 0", avail)
   187  		}
   188  		if inFlight := tc.cc.InFlight(); inFlight != 0 {
   189  			t.Errorf("after connection is closed, cc.InFlight() = %v, want 0", inFlight)
   190  		}
   191  	})
   192  }
   193  
   194  // A testQUICConn wraps a *quic.Conn and provides methods for inspecting it.
   195  type testQUICConn struct {
   196  	t       testing.TB
   197  	qconn   *quic.Conn
   198  	streams map[streamType][]*testQUICStream
   199  }
   200  
   201  func newTestQUICConn(t testing.TB, qconn *quic.Conn) *testQUICConn {
   202  	tq := &testQUICConn{
   203  		t:       t,
   204  		qconn:   qconn,
   205  		streams: make(map[streamType][]*testQUICStream),
   206  	}
   207  
   208  	go tq.acceptStreams(t.Context())
   209  
   210  	t.Cleanup(func() {
   211  		tq.qconn.Close()
   212  	})
   213  	return tq
   214  }
   215  
   216  func (tq *testQUICConn) acceptStreams(ctx context.Context) {
   217  	for {
   218  		qst, err := tq.qconn.AcceptStream(ctx)
   219  		if err != nil {
   220  			return
   221  		}
   222  		st := newStream(qst)
   223  		stype := streamTypeRequest
   224  		if qst.IsReadOnly() {
   225  			v, err := st.readVarint()
   226  			if err != nil {
   227  				tq.t.Errorf("error reading stream type from unidirectional stream: %v", err)
   228  				continue
   229  			}
   230  			stype = streamType(v)
   231  		}
   232  		tq.streams[stype] = append(tq.streams[stype], newTestQUICStream(tq.t, st))
   233  	}
   234  }
   235  
   236  func (tq *testQUICConn) newStream(stype streamType) *testQUICStream {
   237  	tq.t.Helper()
   238  	var qs *quic.Stream
   239  	var err error
   240  	if stype == streamTypeRequest {
   241  		qs, err = tq.qconn.NewStream(canceledCtx)
   242  	} else {
   243  		qs, err = tq.qconn.NewSendOnlyStream(canceledCtx)
   244  	}
   245  	if err != nil {
   246  		tq.t.Fatal(err)
   247  	}
   248  	st := newStream(qs)
   249  	if stype != streamTypeRequest {
   250  		st.writeVarint(int64(stype))
   251  		if err := st.Flush(); err != nil {
   252  			tq.t.Fatal(err)
   253  		}
   254  	}
   255  	return newTestQUICStream(tq.t, st)
   256  }
   257  
   258  // wantNotClosed asserts that the peer has not closed the connection.
   259  func (tq *testQUICConn) wantNotClosed(reason string) {
   260  	t := tq.t
   261  	t.Helper()
   262  	synctest.Wait()
   263  	err := tq.qconn.Wait(canceledCtx)
   264  	if !errors.Is(err, context.Canceled) {
   265  		t.Fatalf("%v: want QUIC connection to be alive; closed with error: %v", reason, err)
   266  	}
   267  }
   268  
   269  // wantClosed asserts that the peer has closed the connection
   270  // with the provided error code.
   271  func (tq *testQUICConn) wantClosed(reason string, want error) {
   272  	t := tq.t
   273  	t.Helper()
   274  	synctest.Wait()
   275  
   276  	if e, ok := want.(http3Error); ok {
   277  		want = &quic.ConnectionCloseError{Code: uint64(e)}
   278  	}
   279  	got := tq.qconn.Wait(canceledCtx)
   280  	if errors.Is(got, context.Canceled) {
   281  		t.Fatalf("%v: want QUIC connection closed, but it is not", reason)
   282  	}
   283  	if !errors.Is(got, want) {
   284  		t.Fatalf("%v: connection closed with error: %v; want %v", reason, got, want)
   285  	}
   286  }
   287  
   288  // wantStream asserts that a stream of a given type has been created,
   289  // and returns that stream.
   290  func (tq *testQUICConn) wantStream(stype streamType) *testQUICStream {
   291  	tq.t.Helper()
   292  	synctest.Wait()
   293  	if len(tq.streams[stype]) == 0 {
   294  		tq.t.Fatalf("expected a %v stream to be created, but none were", stype)
   295  	}
   296  	ts := tq.streams[stype][0]
   297  	tq.streams[stype] = tq.streams[stype][1:]
   298  	return ts
   299  }
   300  
   301  // testQUICStream wraps a QUIC stream and provides methods for inspecting it.
   302  type testQUICStream struct {
   303  	t testing.TB
   304  	*stream
   305  }
   306  
   307  func newTestQUICStream(t testing.TB, st *stream) *testQUICStream {
   308  	st.stream.SetReadContext(canceledCtx)
   309  	st.stream.SetWriteContext(canceledCtx)
   310  	return &testQUICStream{
   311  		t:      t,
   312  		stream: st,
   313  	}
   314  }
   315  
   316  func (ts *testQUICStream) wantIdle(reason string) {
   317  	ts.t.Helper()
   318  	synctest.Wait()
   319  	qs := ts.stream.stream
   320  	qs.SetReadContext(canceledCtx)
   321  	if _, err := qs.Read(make([]byte, 1)); !errors.Is(err, context.Canceled) {
   322  		ts.t.Fatalf("%v: want stream to be idle, but stream has content", reason)
   323  	}
   324  	qs.SetReadContext(context.Background())
   325  }
   326  
   327  // wantFrameHeader calls readFrameHeader and asserts that the frame is of a given type.
   328  func (ts *testQUICStream) wantFrameHeader(reason string, wantType frameType) {
   329  	ts.t.Helper()
   330  	synctest.Wait()
   331  	gotType, err := ts.readFrameHeader()
   332  	if err != nil {
   333  		ts.t.Fatalf("%v: failed to read frame header: %v", reason, err)
   334  	}
   335  	if gotType != wantType {
   336  		ts.t.Fatalf("%v: got frame type %v, want %v", reason, gotType, wantType)
   337  	}
   338  }
   339  
   340  // wantHeaders reads a HEADERS frame.
   341  // If want is nil, the contents of the frame are ignored.
   342  func (ts *testQUICStream) wantHeaders(want http.Header) {
   343  	ts.t.Helper()
   344  	synctest.Wait()
   345  	ftype, err := ts.readFrameHeader()
   346  	if err != nil {
   347  		ts.t.Fatalf("want HEADERS frame, got error: %v", err)
   348  	}
   349  	if ftype != frameTypeHeaders {
   350  		ts.t.Fatalf("want HEADERS frame, got: %v", ftype)
   351  	}
   352  
   353  	if want == nil {
   354  		if err := ts.discardFrame(); err != nil {
   355  			ts.t.Fatalf("discardFrame: %v", err)
   356  		}
   357  		return
   358  	}
   359  
   360  	got := make(http.Header)
   361  	var dec qpackDecoder
   362  	err = dec.decode(ts.stream, func(_ indexType, name, value string) error {
   363  		got.Add(name, value)
   364  		return nil
   365  	})
   366  	if diff := diffHeaders(got, want); diff != "" {
   367  		ts.t.Fatalf("unexpected response headers:\n%v", diff)
   368  	}
   369  	if err := ts.endFrame(); err != nil {
   370  		ts.t.Fatalf("endFrame: %v", err)
   371  	}
   372  }
   373  
   374  // wantSomeHeaders reads a HEADERS frame and asserts that want is a subset of
   375  // the read HEADERS frame.
   376  // This is like wantHeaders, but headers that are in the HEADERS frame but not
   377  // in want are ignored.
   378  func (ts *testQUICStream) wantSomeHeaders(want http.Header) {
   379  	ts.t.Helper()
   380  	synctest.Wait()
   381  	ftype, err := ts.readFrameHeader()
   382  	if err != nil {
   383  		ts.t.Fatalf("want HEADERS frame, got error: %v", err)
   384  	}
   385  	if ftype != frameTypeHeaders {
   386  		ts.t.Fatalf("want HEADERS frame, got: %v", ftype)
   387  	}
   388  
   389  	if want == nil {
   390  		panic("use wantHeaders(nil) instead to ignore the content of the frame")
   391  	}
   392  
   393  	got := make(http.Header)
   394  	var dec qpackDecoder
   395  	err = dec.decode(ts.stream, func(_ indexType, name, value string) error {
   396  		got.Add(name, value)
   397  		return nil
   398  	})
   399  	for name := range got {
   400  		if _, ok := want[name]; !ok {
   401  			delete(got, name)
   402  		}
   403  	}
   404  	if diff := diffHeaders(got, want); diff != "" {
   405  		ts.t.Fatalf("unexpected response headers:\n%v", diff)
   406  	}
   407  	if err := ts.endFrame(); err != nil {
   408  		ts.t.Fatalf("endFrame: %v", err)
   409  	}
   410  }
   411  
   412  func (ts *testQUICStream) encodeHeaders(h http.Header) []byte {
   413  	ts.t.Helper()
   414  	var enc qpackEncoder
   415  	return enc.encode(func(yield func(itype indexType, name, value string)) {
   416  		names := slices.Collect(maps.Keys(h))
   417  		slices.Sort(names)
   418  		for _, k := range names {
   419  			for _, v := range h[k] {
   420  				yield(mayIndex, k, v)
   421  			}
   422  		}
   423  	})
   424  }
   425  
   426  func (ts *testQUICStream) writeHeaders(h http.Header) {
   427  	ts.t.Helper()
   428  	headers := ts.encodeHeaders(h)
   429  	ts.writeVarint(int64(frameTypeHeaders))
   430  	ts.writeVarint(int64(len(headers)))
   431  	ts.Write(headers)
   432  	if err := ts.Flush(); err != nil {
   433  		ts.t.Fatalf("flushing HEADERS frame: %v", err)
   434  	}
   435  }
   436  
   437  // writeHeadersRaw is just like writeHeaders, but avoids qpackEncoder.encode,
   438  // which will automatically make sure that headers are encoded correctly, e.g.
   439  // making field names all lowercase, skipping non-ASCII names.
   440  // This method can be used to test that we properly reject invalid headers.
   441  func (ts *testQUICStream) writeHeadersRaw(h http.Header) {
   442  	ts.t.Helper()
   443  	var b []byte
   444  	b = appendPrefixedInt(b, 0, 8, 0) // Required Insert Count
   445  	b = appendPrefixedInt(b, 0, 7, 0) // Delta Base
   446  
   447  	names := slices.Collect(maps.Keys(h))
   448  	slices.Sort(names)
   449  	for _, k := range names {
   450  		for _, v := range h[k] {
   451  			if i, ok := staticTableByNameValue[tableEntry{k, v}]; ok {
   452  				b = appendIndexedFieldLine(b, staticTable, i)
   453  			} else if i, ok := staticTableByName[k]; ok {
   454  				b = appendLiteralFieldLineWithNameReference(b, staticTable, mayIndex, i, v)
   455  			} else {
   456  				b = appendLiteralFieldLineWithLiteralName(b, mayIndex, k, v)
   457  			}
   458  		}
   459  	}
   460  	headers := b
   461  	ts.writeVarint(int64(frameTypeHeaders))
   462  	ts.writeVarint(int64(len(headers)))
   463  	ts.Write(headers)
   464  	if err := ts.Flush(); err != nil {
   465  		ts.t.Fatalf("flushing HEADERS frame: %v", err)
   466  	}
   467  }
   468  
   469  func (ts *testQUICStream) writeData(b []byte) {
   470  	ts.t.Helper()
   471  	ts.writeVarint(int64(frameTypeData))
   472  	ts.writeVarint(int64(len(b)))
   473  	ts.Write(b)
   474  	if err := ts.Flush(); err != nil {
   475  		ts.t.Fatalf("flushing DATA frame: %v", err)
   476  	}
   477  }
   478  
   479  func (ts *testQUICStream) wantData(want []byte) {
   480  	ts.t.Helper()
   481  	synctest.Wait()
   482  	ftype, err := ts.readFrameHeader()
   483  	if err != nil {
   484  		ts.t.Fatalf("want DATA frame, got error: %v", err)
   485  	}
   486  	if ftype != frameTypeData {
   487  		ts.t.Fatalf("want DATA frame, got: %v", ftype)
   488  	}
   489  	got, err := ts.readFrameData()
   490  	if err != nil {
   491  		ts.t.Fatalf("error reading DATA frame: %v", err)
   492  	}
   493  	if !bytes.Equal(got, want) {
   494  		ts.t.Fatalf("got data: {%x}, want {%x}", got, want)
   495  	}
   496  	if err := ts.endFrame(); err != nil {
   497  		ts.t.Fatalf("endFrame: %v", err)
   498  	}
   499  }
   500  
   501  func (ts *testQUICStream) wantClosed(reason string) {
   502  	ts.t.Helper()
   503  	synctest.Wait()
   504  	ftype, err := ts.readFrameHeader()
   505  	if err != io.EOF {
   506  		ts.t.Fatalf("%v: want io.EOF, got %v %v", reason, ftype, err)
   507  	}
   508  }
   509  
   510  func (ts *testQUICStream) wantError(want quic.StreamError) {
   511  	ts.t.Helper()
   512  	synctest.Wait()
   513  	_, err := ts.ReadByte()
   514  	if err == nil {
   515  		ts.t.Fatalf("successfully read from stream; want stream error code %v", want)
   516  	}
   517  	var got quic.StreamError
   518  	if !errors.As(err, &got) {
   519  		ts.t.Fatalf("stream error = %v; want %v", err, want)
   520  	}
   521  	if got != want {
   522  		ts.t.Fatalf("stream error code = %v; want %v", got, want)
   523  	}
   524  }
   525  
   526  func (ts *testQUICStream) wantSettings(f func(settingType, value int64) error) {
   527  	ts.t.Helper()
   528  	synctest.Wait()
   529  	if f == nil {
   530  		f = func(settingType, value int64) error { return nil }
   531  	}
   532  	if err := ts.readSettings(f); err != nil {
   533  		ts.t.Fatalf("f returned an error: %v", err)
   534  	}
   535  }
   536  
   537  func (ts *testQUICStream) wantGoaway(wantID int64) {
   538  	ts.t.Helper()
   539  	synctest.Wait()
   540  	ftype, err := ts.readFrameHeader()
   541  	if err != nil {
   542  		ts.t.Fatalf("want GOAWAY frame, got error: %v", err)
   543  	}
   544  	if ftype != frameTypeGoaway {
   545  		ts.t.Fatalf("want GOAWAY frame, got: %v", ftype)
   546  	}
   547  	gotID, err := ts.readVarint()
   548  	if err != nil {
   549  		ts.t.Fatalf("failed reading GOAWAY frame, got error: %v", err)
   550  	}
   551  	if gotID != wantID {
   552  		ts.t.Fatalf("got stream ID %v from GOAWAY frame, want %v stream ID", gotID, wantID)
   553  	}
   554  }
   555  
   556  func (ts *testQUICStream) writePushPromise(pushID int64, h http.Header) {
   557  	ts.t.Helper()
   558  	headers := ts.encodeHeaders(h)
   559  	ts.writeVarint(int64(frameTypePushPromise))
   560  	ts.writeVarint(int64(sizeVarint(uint64(pushID)) + len(headers)))
   561  	ts.writeVarint(pushID)
   562  	ts.Write(headers)
   563  	if err := ts.Flush(); err != nil {
   564  		ts.t.Fatalf("flushing PUSH_PROMISE frame: %v", err)
   565  	}
   566  }
   567  
   568  func diffHeaders(got, want http.Header) string {
   569  	// nil and 0-length non-nil are equal.
   570  	if len(got) == 0 && len(want) == 0 {
   571  		return ""
   572  	}
   573  	// We could do a more sophisticated diff here.
   574  	// DeepEqual is good enough for now.
   575  	if reflect.DeepEqual(got, want) {
   576  		return ""
   577  	}
   578  	return fmt.Sprintf("got:  %v\nwant: %v", got, want)
   579  }
   580  
   581  func (ts *testQUICStream) Flush() error {
   582  	err := ts.stream.Flush()
   583  	ts.t.Helper()
   584  	if err != nil {
   585  		ts.t.Errorf("unexpected error flushing stream: %v", err)
   586  	}
   587  	return err
   588  }
   589  
   590  // A testClientConn is a ClientConn on a test network.
   591  type testClientConn struct {
   592  	tr *transport
   593  	cc *clientConn
   594  
   595  	// *testQUICConn is the server half of the connection.
   596  	*testQUICConn
   597  	control *testQUICStream
   598  }
   599  
   600  func newTestClientConnWithHook(t testing.TB, stateHook func()) *testClientConn {
   601  	e1, e2 := newQUICEndpointPair(t)
   602  	tr := &transport{
   603  		endpoint:    e1,
   604  		tr1:         new(http.Transport),
   605  		activeConns: make(map[*clientConn]struct{}),
   606  	}
   607  
   608  	cc, err := tr.dial(t.Context(), e2.LocalAddr().String(), testTLSConfig, stateHook)
   609  	if err != nil {
   610  		t.Fatal(err)
   611  	}
   612  	t.Cleanup(func() {
   613  		cc.Close()
   614  	})
   615  	srvConn, err := e2.Accept(t.Context())
   616  	if err != nil {
   617  		t.Fatal(err)
   618  	}
   619  
   620  	tc := &testClientConn{
   621  		tr:           tr,
   622  		cc:           cc,
   623  		testQUICConn: newTestQUICConn(t, srvConn),
   624  	}
   625  	synctest.Wait()
   626  	return tc
   627  }
   628  
   629  func newTestClientConn(t testing.TB) *testClientConn {
   630  	return newTestClientConnWithHook(t, nil)
   631  }
   632  
   633  // greet performs initial connection handshaking with the client.
   634  func (tc *testClientConn) greet() {
   635  	// Client creates a control stream.
   636  	clientControlStream := tc.wantStream(streamTypeControl)
   637  	clientControlStream.wantFrameHeader(
   638  		"client sends SETTINGS frame on control stream",
   639  		frameTypeSettings)
   640  	clientControlStream.discardFrame()
   641  
   642  	// Server creates a control stream.
   643  	tc.control = tc.newStream(streamTypeControl)
   644  	tc.control.writeVarint(int64(frameTypeSettings))
   645  	tc.control.writeVarint(0) // size
   646  	tc.control.Flush()
   647  
   648  	synctest.Wait()
   649  }
   650  
   651  type testRoundTrip struct {
   652  	t       testing.TB
   653  	resp    *http.Response
   654  	respErr error
   655  }
   656  
   657  func (rt *testRoundTrip) done() bool {
   658  	synctest.Wait()
   659  	return rt.resp != nil || rt.respErr != nil
   660  }
   661  
   662  func (rt *testRoundTrip) result() (*http.Response, error) {
   663  	rt.t.Helper()
   664  	if !rt.done() {
   665  		rt.t.Fatal("RoundTrip is not done; want it to be")
   666  	}
   667  	return rt.resp, rt.respErr
   668  }
   669  
   670  func (rt *testRoundTrip) response() *http.Response {
   671  	rt.t.Helper()
   672  	if !rt.done() {
   673  		rt.t.Fatal("RoundTrip is not done; want it to be")
   674  	}
   675  	if rt.respErr != nil {
   676  		rt.t.Fatalf("RoundTrip returned unexpected error: %v", rt.respErr)
   677  	}
   678  	return rt.resp
   679  }
   680  
   681  // err returns the (possibly nil) error result of RoundTrip.
   682  func (rt *testRoundTrip) err() error {
   683  	rt.t.Helper()
   684  	_, err := rt.result()
   685  	return err
   686  }
   687  
   688  func (rt *testRoundTrip) wantError(reason string) {
   689  	rt.t.Helper()
   690  	synctest.Wait()
   691  	if !rt.done() {
   692  		rt.t.Fatalf("%v: RoundTrip is not done; want it to have returned an error", reason)
   693  	}
   694  	if rt.respErr == nil {
   695  		rt.t.Fatalf("%v: RoundTrip succeeded; want it to have returned an error", reason)
   696  	}
   697  }
   698  
   699  // wantStatus indicates the expected response StatusCode.
   700  func (rt *testRoundTrip) wantStatus(want int) {
   701  	rt.t.Helper()
   702  	if got := rt.response().StatusCode; got != want {
   703  		rt.t.Fatalf("got response status %v, want %v", got, want)
   704  	}
   705  }
   706  
   707  func (rt *testRoundTrip) wantHeaders(want http.Header) {
   708  	rt.t.Helper()
   709  	if diff := diffHeaders(rt.response().Header, want); diff != "" {
   710  		rt.t.Fatalf("unexpected response headers:\n%v", diff)
   711  	}
   712  }
   713  
   714  func (rt *testRoundTrip) wantTrailers(want http.Header) {
   715  	rt.t.Helper()
   716  	if diff := diffHeaders(rt.response().Trailer, want); diff != "" {
   717  		rt.t.Fatalf("unexpected response trailers:\n%v", diff)
   718  	}
   719  }
   720  
   721  // readBody reads the contents of the response body.
   722  func (rt *testRoundTrip) readBody() ([]byte, error) {
   723  	t := rt.t
   724  	t.Helper()
   725  	return io.ReadAll(rt.response().Body)
   726  }
   727  
   728  // wantBody consumes the a body and asserts that it is as expected.
   729  func (rt *testRoundTrip) wantBody(want []byte) {
   730  	t := rt.t
   731  	t.Helper()
   732  	got, err := rt.readBody()
   733  	if err != nil {
   734  		t.Fatalf("unexpected error reading response body: %v", err)
   735  	}
   736  	if !bytes.Equal(got, want) {
   737  		t.Fatalf("unexpected response body:\ngot:  %q\nwant: %q", got, want)
   738  	}
   739  }
   740  
   741  func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip {
   742  	rt := &testRoundTrip{t: tc.t}
   743  	go func() {
   744  		rt.resp, rt.respErr = tc.cc.RoundTrip(req)
   745  	}()
   746  	return rt
   747  }
   748  

View as plain text