// 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) }) }