Files
quic-go/http3/frames_test.go

382 lines
10 KiB
Go

package http3
import (
"bytes"
"context"
"fmt"
"io"
"testing"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/quicvarint"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testFrameParserEOF(t *testing.T, data []byte) {
t.Helper()
for i := range data {
b := make([]byte, i)
copy(b, data[:i])
fp := frameParser{r: bytes.NewReader(b)}
_, err := fp.ParseNext(nil)
require.Error(t, err)
require.ErrorIs(t, err, io.EOF)
}
}
func TestParserReservedFrameType(t *testing.T) {
for _, ft := range []uint64{0x2, 0x6, 0x8, 0x9} {
t.Run(fmt.Sprintf("type %#x", ft), func(t *testing.T) {
client, server := newConnPair(t)
data := quicvarint.Append(nil, ft)
data = quicvarint.Append(data, 6)
data = append(data, []byte("foobar")...)
fp := frameParser{
r: bytes.NewReader(data),
closeConn: client.CloseWithError,
}
_, err := fp.ParseNext(nil)
require.Error(t, err)
require.ErrorContains(t, err, "http3: reserved frame type")
select {
case <-server.Context().Done():
require.ErrorIs(t,
context.Cause(server.Context()),
&quic.ApplicationError{Remote: true, ErrorCode: quic.ApplicationErrorCode(ErrCodeFrameUnexpected)},
)
case <-time.After(time.Second):
t.Fatal("timeout")
}
})
}
}
func TestParserUnknownFrameType(t *testing.T) {
data := quicvarint.Append(nil, 0xdead)
data = quicvarint.Append(data, 6)
data = append(data, []byte("foobar")...)
data = quicvarint.Append(data, 0xbeef)
data = quicvarint.Append(data, 3)
data = append(data, []byte("baz")...)
hf := &headersFrame{Length: 3}
data = hf.Append(data)
data = append(data, []byte("foo")...)
r := bytes.NewReader(data)
fp := frameParser{r: r}
f, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &headersFrame{}, f)
hf = f.(*headersFrame)
require.Equal(t, uint64(3), hf.Length)
payload := make([]byte, 3)
_, err = io.ReadFull(r, payload)
require.NoError(t, err)
require.Equal(t, []byte("foo"), payload)
}
func TestParserHeadersFrame(t *testing.T) {
data := quicvarint.Append(nil, 1) // type byte
data = quicvarint.Append(data, 0x1337)
fp := frameParser{r: bytes.NewReader(data)}
// incomplete data results in an io.EOF
testFrameParserEOF(t, data)
// parse
f1, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &headersFrame{}, f1)
require.Equal(t, uint64(0x1337), f1.(*headersFrame).Length)
// write and parse
fp = frameParser{r: bytes.NewReader(f1.(*headersFrame).Append(nil))}
f2, err := fp.ParseNext(nil)
require.NoError(t, err)
require.Equal(t, f1, f2)
}
func TestDataFrame(t *testing.T) {
data := quicvarint.Append(nil, 0) // type byte
data = quicvarint.Append(data, 0x1337)
fp := frameParser{r: bytes.NewReader(data)}
// incomplete data results in an io.EOF
testFrameParserEOF(t, data)
// parse
f1, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &dataFrame{}, f1)
require.Equal(t, uint64(0x1337), f1.(*dataFrame).Length)
// write and parse
fp = frameParser{r: bytes.NewReader(f1.(*dataFrame).Append(nil))}
f2, err := fp.ParseNext(nil)
require.NoError(t, err)
require.Equal(t, f1, f2)
}
func appendSetting(b []byte, key, value uint64) []byte {
b = quicvarint.Append(b, key)
b = quicvarint.Append(b, value)
return b
}
func TestParserSettingsFrame(t *testing.T) {
settings := appendSetting(nil, 13, 37)
settings = appendSetting(settings, 0xdead, 0xbeef)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
// incomplete data results in an io.EOF
testFrameParserEOF(t, data)
fp := frameParser{r: bytes.NewReader(data)}
frame, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &settingsFrame{}, frame)
sf := frame.(*settingsFrame)
require.Len(t, sf.Other, 2)
require.Equal(t, uint64(37), sf.Other[uint64(13)])
require.Equal(t, uint64(0xbeef), sf.Other[uint64(0xdead)])
// write and parse
fp = frameParser{r: bytes.NewReader(sf.Append(nil))}
f2, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &settingsFrame{}, f2)
sf2 := f2.(*settingsFrame)
require.Len(t, sf2.Other, len(sf.Other))
require.Equal(t, sf.Other, sf2.Other)
}
func TestParserSettingsFrameDuplicateSettings(t *testing.T) {
for _, tc := range []struct {
name string
num uint64
val uint64
}{
{
name: "other setting",
num: 13,
val: 37,
},
{
name: "extended connect",
num: settingExtendedConnect,
val: 1,
},
{
name: "datagram",
num: settingDatagram,
val: 1,
},
} {
t.Run(tc.name, func(t *testing.T) {
settings := appendSetting(nil, tc.num, tc.val)
settings = appendSetting(settings, tc.num, tc.val)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
fp := frameParser{r: bytes.NewReader(data)}
_, err := fp.ParseNext(nil)
require.Error(t, err)
require.EqualError(t, err, fmt.Sprintf("duplicate setting: %d", tc.num))
})
}
}
func TestParserSettingsFrameDatagram(t *testing.T) {
t.Run("enabled", func(t *testing.T) {
testParserSettingsFrameDatagram(t, true)
})
t.Run("disabled", func(t *testing.T) {
testParserSettingsFrameDatagram(t, false)
})
}
func testParserSettingsFrameDatagram(t *testing.T, enabled bool) {
var settings []byte
switch enabled {
case true:
settings = appendSetting(nil, settingDatagram, 1)
case false:
settings = appendSetting(nil, settingDatagram, 0)
}
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
fp := frameParser{r: bytes.NewReader(data)}
f, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &settingsFrame{}, f)
sf := f.(*settingsFrame)
require.Equal(t, enabled, sf.Datagram)
fp = frameParser{r: bytes.NewReader(sf.Append(nil))}
f2, err := fp.ParseNext(nil)
require.NoError(t, err)
require.Equal(t, sf, f2)
}
func TestParserSettingsFrameDatagramInvalidValue(t *testing.T) {
settings := quicvarint.Append(nil, settingDatagram)
settings = quicvarint.Append(settings, 1337)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
fp := frameParser{r: bytes.NewReader(data)}
_, err := fp.ParseNext(nil)
require.EqualError(t, err, "invalid value for SETTINGS_H3_DATAGRAM: 1337")
}
func TestParserSettingsFrameExtendedConnect(t *testing.T) {
t.Run("enabled", func(t *testing.T) {
testParserSettingsFrameExtendedConnect(t, true)
})
t.Run("disabled", func(t *testing.T) {
testParserSettingsFrameExtendedConnect(t, false)
})
}
func testParserSettingsFrameExtendedConnect(t *testing.T, enabled bool) {
var settings []byte
switch enabled {
case true:
settings = appendSetting(nil, settingExtendedConnect, 1)
case false:
settings = appendSetting(nil, settingExtendedConnect, 0)
}
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
fp := frameParser{r: bytes.NewReader(data)}
f, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &settingsFrame{}, f)
sf := f.(*settingsFrame)
require.Equal(t, enabled, sf.ExtendedConnect)
fp = frameParser{r: bytes.NewReader(sf.Append(nil))}
f2, err := fp.ParseNext(nil)
require.NoError(t, err)
require.Equal(t, sf, f2)
}
func TestParserSettingsFrameExtendedConnectInvalidValue(t *testing.T) {
settings := quicvarint.Append(nil, settingExtendedConnect)
settings = quicvarint.Append(settings, 1337)
data := quicvarint.Append(nil, 4) // type byte
data = quicvarint.Append(data, uint64(len(settings)))
data = append(data, settings...)
fp := frameParser{r: bytes.NewReader(data)}
_, err := fp.ParseNext(nil)
require.EqualError(t, err, "invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: 1337")
}
func TestParserGoAwayFrame(t *testing.T) {
data := quicvarint.Append(nil, 7) // type byte
data = quicvarint.Append(data, uint64(quicvarint.Len(100)))
data = quicvarint.Append(data, 100)
// incomplete data results in an io.EOF
testFrameParserEOF(t, data)
fp := frameParser{r: bytes.NewReader(data)}
f, err := fp.ParseNext(nil)
require.NoError(t, err)
require.IsType(t, &goAwayFrame{}, f)
require.Equal(t, quic.StreamID(100), f.(*goAwayFrame).StreamID)
// write and parse
fp = frameParser{r: bytes.NewReader(f.(*goAwayFrame).Append(nil))}
f2, err := fp.ParseNext(nil)
require.NoError(t, err)
require.Equal(t, f, f2)
}
func TestParserHijacking(t *testing.T) {
t.Run("hijacking", func(t *testing.T) {
testParserHijacking(t, true)
})
t.Run("not hijacking", func(t *testing.T) {
testParserHijacking(t, false)
})
}
func testParserHijacking(t *testing.T, hijack bool) {
b := quicvarint.Append(nil, 1337)
if hijack {
b = append(b, "foobar"...)
} else {
// if the stream is not hijacked, this will be treated as an unknown frame
b = quicvarint.Append(b, 11)
b = append(b, []byte("lorem ipsum")...)
b = (&dataFrame{Length: 6}).Append(b)
b = append(b, []byte("foobar")...)
}
var called bool
r := bytes.NewReader(b)
fp := frameParser{
r: r,
unknownFrameHandler: func(ft FrameType, e error) (hijacked bool, err error) {
called = true
require.NoError(t, e)
require.Equal(t, FrameType(1337), ft)
if !hijack {
return false, nil
}
b := make([]byte, 6)
_, err = io.ReadFull(r, b)
require.NoError(t, err)
require.Equal(t, "foobar", string(b))
return true, nil
},
}
f, err := fp.ParseNext(nil)
require.True(t, called)
if hijack {
require.ErrorIs(t, err, errHijacked)
return
}
require.NoError(t, err)
require.IsType(t, &dataFrame{}, f)
df := f.(*dataFrame)
require.Equal(t, uint64(6), df.Length)
payload := make([]byte, 6)
_, err = io.ReadFull(r, payload)
require.NoError(t, err)
require.Equal(t, "foobar", string(payload))
}
type errReader struct{ err error }
func (e errReader) Read([]byte) (int, error) { return 0, e.err }
func TestParserHijackError(t *testing.T) {
var called bool
fp := frameParser{
r: errReader{err: assert.AnError},
unknownFrameHandler: func(ft FrameType, e error) (hijacked bool, err error) {
require.EqualError(t, e, assert.AnError.Error())
require.Zero(t, ft)
called = true
return true, nil
},
}
_, err := fp.ParseNext(nil)
require.ErrorIs(t, err, errHijacked)
require.True(t, called)
}