1
2
3
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
28
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
44
45
46 func TestTransportInitEndpointError(t *testing.T) {
47 conn := &unusablePacketConn{}
48 tr := &transport{
49 tr1: new(http.Transport),
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
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
78
79
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
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
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
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 {
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
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
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
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
270
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
289
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
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
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
341
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
375
376
377
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
438
439
440
441 func (ts *testQUICStream) writeHeadersRaw(h http.Header) {
442 ts.t.Helper()
443 var b []byte
444 b = appendPrefixedInt(b, 0, 8, 0)
445 b = appendPrefixedInt(b, 0, 7, 0)
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
570 if len(got) == 0 && len(want) == 0 {
571 return ""
572 }
573
574
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
591 type testClientConn struct {
592 tr *transport
593 cc *clientConn
594
595
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
634 func (tc *testClientConn) greet() {
635
636 clientControlStream := tc.wantStream(streamTypeControl)
637 clientControlStream.wantFrameHeader(
638 "client sends SETTINGS frame on control stream",
639 frameTypeSettings)
640 clientControlStream.discardFrame()
641
642
643 tc.control = tc.newStream(streamTypeControl)
644 tc.control.writeVarint(int64(frameTypeSettings))
645 tc.control.writeVarint(0)
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
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
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
722 func (rt *testRoundTrip) readBody() ([]byte, error) {
723 t := rt.t
724 t.Helper()
725 return io.ReadAll(rt.response().Body)
726 }
727
728
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