blob: e9b5b4189df84eef5de6f591a259e9e3e91cd778 [file] [log] [blame]
// 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)
})
}