| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build go1.24 && goexperiment.synctest |
| |
| package http3 |
| |
| import ( |
| "testing" |
| "testing/synctest" |
| ) |
| |
| // Tests which apply to both client and server connections. |
| |
| func TestConnCreatesControlStream(t *testing.T) { |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| controlStream := tc.wantStream(streamTypeControl) |
| controlStream.wantFrameHeader( |
| "server sends SETTINGS frame on control stream", |
| frameTypeSettings) |
| controlStream.discardFrame() |
| }) |
| } |
| |
| func TestConnUnknownUnidirectionalStream(t *testing.T) { |
| // "Recipients of unknown stream types MUST either abort reading of the stream |
| // or discard incoming data without further processing." |
| // https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2-7 |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| st := tc.newStream(0x21) // reserved stream type |
| |
| // The endpoint should send a STOP_SENDING for this stream, |
| // but it should not close the connection. |
| synctest.Wait() |
| if _, err := st.Write([]byte("hello")); err == nil { |
| t.Fatalf("write to send-only stream with an unknown type succeeded; want error") |
| } |
| tc.wantNotClosed("after receiving unknown unidirectional stream type") |
| }) |
| } |
| |
| func TestConnUnknownSettings(t *testing.T) { |
| // "An implementation MUST ignore any [settings] parameter with |
| // an identifier it does not understand." |
| // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-9 |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| controlStream := tc.newStream(streamTypeControl) |
| controlStream.writeSettings(0x1f+0x21, 0) // reserved settings type |
| controlStream.Flush() |
| tc.wantNotClosed("after receiving unknown settings") |
| }) |
| } |
| |
| func TestConnInvalidSettings(t *testing.T) { |
| // "These reserved settings MUST NOT be sent, and their receipt MUST |
| // be treated as a connection error of type H3_SETTINGS_ERROR." |
| // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-5 |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| controlStream := tc.newStream(streamTypeControl) |
| controlStream.writeSettings(0x02, 0) // HTTP/2 SETTINGS_ENABLE_PUSH |
| controlStream.Flush() |
| tc.wantClosed("invalid setting", errH3SettingsError) |
| }) |
| } |
| |
| func TestConnDuplicateStream(t *testing.T) { |
| for _, stype := range []streamType{ |
| streamTypeControl, |
| streamTypeEncoder, |
| streamTypeDecoder, |
| } { |
| t.Run(stype.String(), func(t *testing.T) { |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| _ = tc.newStream(stype) |
| tc.wantNotClosed("after creating one " + stype.String() + " stream") |
| |
| // Opening a second control, encoder, or decoder stream |
| // is a protocol violation. |
| _ = tc.newStream(stype) |
| tc.wantClosed("duplicate stream", errH3StreamCreationError) |
| }) |
| }) |
| } |
| } |
| |
| func TestConnUnknownFrames(t *testing.T) { |
| for _, stype := range []streamType{ |
| streamTypeControl, |
| } { |
| t.Run(stype.String(), func(t *testing.T) { |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| st := tc.newStream(stype) |
| |
| if stype == streamTypeControl { |
| // First frame on the control stream must be settings. |
| st.writeVarint(int64(frameTypeSettings)) |
| st.writeVarint(0) // size |
| } |
| |
| data := "frame content" |
| st.writeVarint(0x1f + 0x21) // reserved frame type |
| st.writeVarint(int64(len(data))) // size |
| st.Write([]byte(data)) |
| st.Flush() |
| |
| tc.wantNotClosed("after writing unknown frame") |
| }) |
| }) |
| } |
| } |
| |
| func TestConnInvalidFrames(t *testing.T) { |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| control := tc.newStream(streamTypeControl) |
| |
| // SETTINGS frame. |
| control.writeVarint(int64(frameTypeSettings)) |
| control.writeVarint(0) // size |
| |
| // DATA frame (invalid on the control stream). |
| control.writeVarint(int64(frameTypeData)) |
| control.writeVarint(0) // size |
| control.Flush() |
| tc.wantClosed("after writing DATA frame to control stream", errH3FrameUnexpected) |
| }) |
| } |
| |
| func TestConnPeerCreatesBadUnidirectionalStream(t *testing.T) { |
| runConnTest(t, func(t testing.TB, tc *testQUICConn) { |
| // Create and close a stream without sending the unidirectional stream header. |
| qs, err := tc.qconn.NewSendOnlyStream(canceledCtx) |
| if err != nil { |
| t.Fatal(err) |
| } |
| st := newTestQUICStream(tc.t, newStream(qs)) |
| st.stream.stream.Close() |
| |
| tc.wantClosed("after peer creates and closes uni stream", errH3StreamCreationError) |
| }) |
| } |
| |
| func runConnTest(t *testing.T, f func(testing.TB, *testQUICConn)) { |
| t.Helper() |
| runSynctestSubtest(t, "client", func(t testing.TB) { |
| tc := newTestClientConn(t) |
| f(t, tc.testQUICConn) |
| }) |
| } |