"time"
"github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/quic-go/quicvarint"
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
return
}
+ if c.qlogger != nil {
+ c.qlogger.RecordEvent(qlog.FrameParsed{
+ StreamID: str.StreamID(),
+ Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: goaway.StreamID}},
+ })
+ }
if goaway.StreamID%4 != 0 { // client-initiated, bidirectional streams
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
return
"time"
"github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/quicvarint"
+ "github.com/quic-go/quic-go/testutils/events"
"github.com/stretchr/testify/require"
)
b = (&settingsFrame{Other: map[uint64]uint64{settingExtendedConnect: 1337}}).Append(b)
testConnControlStreamFailures(t, b, nil, ErrCodeFrameError)
})
+
t.Run("not a GOAWAY", func(t *testing.T) {
b := (&settingsFrame{}).Append(nil)
// GOAWAY is the only allowed frame type after SETTINGS
b = (&headersFrame{}).Append(b)
testConnControlStreamFailures(t, b, nil, ErrCodeFrameUnexpected)
})
+
t.Run("stream closed before GOAWAY", func(t *testing.T) {
testConnControlStreamFailures(t, (&settingsFrame{}).Append(nil), io.EOF, ErrCodeClosedCriticalStream)
})
+
t.Run("stream reset before GOAWAY", func(t *testing.T) {
testConnControlStreamFailures(t,
(&settingsFrame{}).Append(nil),
ErrCodeClosedCriticalStream,
)
})
+
t.Run("invalid stream ID", func(t *testing.T) {
data := (&settingsFrame{}).Append(nil)
data = (&goAwayFrame{StreamID: 1}).Append(data)
testConnControlStreamFailures(t, data, nil, ErrCodeIDError)
})
+
t.Run("increased stream ID", func(t *testing.T) {
data := (&settingsFrame{}).Append(nil)
data = (&goAwayFrame{StreamID: 4}).Append(data)
}
func testConnGoAway(t *testing.T, withStream bool) {
- clientConn, serverConn := newConnPair(t)
+ var clientEventRecorder events.Recorder
+ clientConn, serverConn := newConnPairWithRecorder(t, &clientEventRecorder, nil)
conn := newConnection(
clientConn.Context(),
case <-time.After(time.Second):
t.Fatal("timeout waiting for close")
}
+
+ framesParsed := clientEventRecorder.Events(qlog.FrameParsed{})
+ var goawayFramesParsed []qlog.FrameParsed
+ for _, ev := range framesParsed {
+ if _, ok := ev.(qlog.FrameParsed).Frame.Frame.(qlog.GoAwayFrame); ok {
+ goawayFramesParsed = append(goawayFramesParsed, ev.(qlog.FrameParsed))
+ }
+ }
+ require.Equal(t,
+ []qlog.FrameParsed{{
+ StreamID: 3,
+ Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: 8}},
+ }},
+ goawayFramesParsed,
+ )
}
func TestConnRejectPushStream(t *testing.T) {
PayloadLength int // length of the packet payload, excluding AEAD tag
}
+func (i RawInfo) HasValues() bool {
+ return i.Length != 0 || i.PayloadLength != 0
+}
+
func (i RawInfo) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Uint(uint64(e.StreamID)))
- h.WriteToken(jsontext.String("raw"))
- if err := e.Raw.encode(enc); err != nil {
- return err
+ if e.Raw.HasValues() {
+ h.WriteToken(jsontext.String("raw"))
+ if err := e.Raw.encode(enc); err != nil {
+ return err
+ }
}
h.WriteToken(jsontext.String("frame"))
if err := e.Frame.encode(enc); err != nil {
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Uint(uint64(e.StreamID)))
- h.WriteToken(jsontext.String("raw"))
- if err := e.Raw.encode(enc); err != nil {
- return err
+ if e.Raw.HasValues() {
+ h.WriteToken(jsontext.String("raw"))
+ if err := e.Raw.encode(enc); err != nil {
+ return err
+ }
}
h.WriteToken(jsontext.String("frame"))
if err := e.Frame.encode(enc); err != nil {
package qlog
import (
+ "github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
return frame.encode(enc)
case HeadersFrame:
return frame.encode(enc)
+ case GoAwayFrame:
+ return frame.encode(enc)
}
// This shouldn't happen if the code is correctly logging frames.
// Write a null token to produce valid JSON.
h.WriteToken(jsontext.EndObject)
return h.err
}
+
+// A GoAwayFrame is a GOAWAY frame
+type GoAwayFrame struct {
+ StreamID quic.StreamID
+}
+
+func (f *GoAwayFrame) encode(enc *jsontext.Encoder) error {
+ h := encoderHelper{enc: enc}
+ h.WriteToken(jsontext.BeginObject)
+ h.WriteToken(jsontext.String("frame_type"))
+ h.WriteToken(jsontext.String("goaway"))
+ h.WriteToken(jsontext.String("id"))
+ h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
+ h.WriteToken(jsontext.EndObject)
+ return h.err
+}
},
})
}
+
+func TestGoAwayFrame(t *testing.T) {
+ check(t, GoAwayFrame{StreamID: 1337}, map[string]any{
+ "frame_type": "goaway",
+ "id": 1337,
+ })
+}
"time"
"github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/quic-go/quicvarint"
// gracefully closed, send GOAWAY frame and wait for requests to complete or grace period to end
// new requests will be rejected and shouldn't be sent
+ if qlogger != nil {
+ qlogger.RecordEvent(qlog.FrameCreated{
+ StreamID: ctrlStr.StreamID(),
+ Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: nextStreamID}},
+ })
+ }
wg.Add(1)
// Send the GOAWAY frame in a separate Goroutine.
// Sending might block if the peer didn't grant enough flow control credit.