Skip to content

Commit 5bda71a

Browse files
neildgopherbot
authored andcommitted
internal/http3: define connection and stream error types
HTTP/3 distinguishes between connection errors which result in an entire connection closing, and stream errors which only terminate a single request stream. Define internal types to represent these two types of error. For golang/go#70914 Change-Id: I907f395adc82a683b5c2eda65f936b1ab4904ffb Reviewed-on: https://go-review.googlesource.com/c/net/+/644117 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Damien Neil <[email protected]>
1 parent 3c1185a commit 5bda71a

File tree

4 files changed

+66
-27
lines changed

4 files changed

+66
-27
lines changed

internal/http3/errors.go

+20
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,23 @@ func (e http3Error) Error() string {
8282
}
8383
return fmt.Sprintf("H3_ERROR_%v", int(e))
8484
}
85+
86+
// A streamError is an error which terminates a stream, but not the connection.
87+
// https://www.rfc-editor.org/rfc/rfc9114.html#section-8-1
88+
type streamError struct {
89+
code http3Error
90+
message string
91+
}
92+
93+
func (e *streamError) Error() string { return e.message }
94+
func (e *streamError) Unwrap() error { return e.code }
95+
96+
// A connectionError is an error which results in the entire connection closing.
97+
// https://www.rfc-editor.org/rfc/rfc9114.html#section-8-2
98+
type connectionError struct {
99+
code http3Error
100+
message string
101+
}
102+
103+
func (e *connectionError) Error() string { return e.message }
104+
func (e *connectionError) Unwrap() error { return e.code }

internal/http3/stream.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ func (st *stream) readFrameHeader() (ftype frameType, err error) {
8787
// It returns an error if the entire contents of a frame have not been read.
8888
func (st *stream) endFrame() error {
8989
if st.lim != 0 {
90-
return errH3FrameError
90+
return &connectionError{
91+
code: errH3FrameError,
92+
message: "invalid HTTP/3 frame",
93+
}
9194
}
9295
st.lim = -1
9396
return nil
@@ -160,9 +163,9 @@ func (st *stream) discardUnknownFrame(ftype frameType) error {
160163
frameTypePushPromise,
161164
frameTypeGoaway,
162165
frameTypeMaxPushID:
163-
return &quic.ApplicationError{
164-
Code: uint64(errH3FrameUnexpected),
165-
Reason: "unexpected " + ftype.String() + " frame",
166+
return &connectionError{
167+
code: errH3FrameUnexpected,
168+
message: "unexpected " + ftype.String() + " frame",
166169
}
167170
}
168171
return st.discardFrame()
@@ -174,7 +177,7 @@ func (st *stream) discardFrame() error {
174177
for range st.lim {
175178
_, err := st.stream.ReadByte()
176179
if err != nil {
177-
return errH3FrameError
180+
return &streamError{errH3FrameError, err.Error()}
178181
}
179182
}
180183
st.lim = -1
@@ -250,7 +253,10 @@ func (st *stream) recordBytesRead(n int) error {
250253
st.lim -= int64(n)
251254
if st.lim < 0 {
252255
st.stream = nil // panic if we try to read again
253-
return errH3FrameError
256+
return &connectionError{
257+
code: errH3FrameError,
258+
message: "invalid HTTP/3 frame",
259+
}
254260
}
255261
return nil
256262
}

internal/http3/stream_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package http3
88

99
import (
1010
"bytes"
11+
"errors"
1112
"io"
1213
"testing"
1314

@@ -142,7 +143,7 @@ func TestStreamReadFrameUnderflow(t *testing.T) {
142143
t.Fatalf("st.Read() = %v", err)
143144
}
144145
// We have not consumed the full frame: Error.
145-
if err := st2.endFrame(); err != errH3FrameError {
146+
if err := st2.endFrame(); !errors.Is(err, errH3FrameError) {
146147
t.Fatalf("st.endFrame before end: %v, want errH3FrameError", err)
147148
}
148149
}
@@ -178,7 +179,7 @@ func TestStreamReadFrameOverflow(t *testing.T) {
178179
if _, err := st2.readFrameHeader(); err != nil {
179180
t.Fatalf("st.readFrameHeader() = %v", err)
180181
}
181-
if _, err := io.ReadFull(st2, make([]byte, size+1)); err != errH3FrameError {
182+
if _, err := io.ReadFull(st2, make([]byte, size+1)); !errors.Is(err, errH3FrameError) {
182183
t.Fatalf("st.Read past end of frame: %v, want errH3FrameError", err)
183184
}
184185
}

internal/http3/transport.go

+31-19
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ func (cc *ClientConn) acceptStreams() {
148148
// "Clients MUST treat receipt of a server-initiated bidirectional
149149
// stream as a connection error of type H3_STREAM_CREATION_ERROR [...]"
150150
// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.1-3
151-
cc.qconn.Abort(&quic.ApplicationError{
152-
Code: uint64(errH3StreamCreationError),
153-
Reason: "server created bidirectional stream",
151+
cc.abort(&connectionError{
152+
code: errH3StreamCreationError,
153+
message: "server created bidirectional stream",
154154
})
155155
return
156156
}
@@ -162,9 +162,9 @@ func (cc *ClientConn) handleStream(st *stream) {
162162
// Unidirectional stream header: One varint with the stream type.
163163
stype, err := st.readVarint()
164164
if err != nil {
165-
cc.qconn.Abort(&quic.ApplicationError{
166-
Code: uint64(errH3StreamCreationError),
167-
Reason: "error reading unidirectional stream header",
165+
cc.abort(&connectionError{
166+
code: errH3StreamCreationError,
167+
message: "error reading unidirectional stream header",
168168
})
169169
return
170170
}
@@ -190,13 +190,13 @@ func (cc *ClientConn) handleStream(st *stream) {
190190
err = nil
191191
}
192192
if err == io.EOF {
193-
err = &quic.ApplicationError{
194-
Code: uint64(errH3ClosedCriticalStream),
195-
Reason: streamType(stype).String() + " stream closed",
193+
err = &connectionError{
194+
code: errH3ClosedCriticalStream,
195+
message: streamType(stype).String() + " stream closed",
196196
}
197197
}
198198
if err != nil {
199-
cc.qconn.Abort(err)
199+
cc.abort(err)
200200
}
201201
}
202202

@@ -205,9 +205,9 @@ func (cc *ClientConn) checkStreamCreation(stype streamType, name string) error {
205205
defer cc.mu.Unlock()
206206
bit := uint8(1) << stype
207207
if cc.streamsCreated&bit != 0 {
208-
return &quic.ApplicationError{
209-
Code: uint64(errH3StreamCreationError),
210-
Reason: "multiple " + name + " streams created",
208+
return &connectionError{
209+
code: errH3StreamCreationError,
210+
message: "multiple " + name + " streams created",
211211
}
212212
}
213213
cc.streamsCreated |= bit
@@ -251,9 +251,9 @@ func (cc *ClientConn) handleControlStream(st *stream) error {
251251
// greater than currently allowed on the connection,
252252
// this MUST be treated as a connection error of type H3_ID_ERROR."
253253
// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-7
254-
return &quic.ApplicationError{
255-
Code: uint64(errH3IDError),
256-
Reason: "CANCEL_PUSH received when no MAX_PUSH_ID has been sent",
254+
return &connectionError{
255+
code: errH3IDError,
256+
message: "CANCEL_PUSH received when no MAX_PUSH_ID has been sent",
257257
}
258258
case frameTypeGoaway:
259259
// TODO: Wait for requests to complete before closing connection.
@@ -293,8 +293,20 @@ func (cc *ClientConn) handlePushStream(*stream) error {
293293
// "A client MUST treat receipt of a push stream as a connection error
294294
// of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent [...]"
295295
// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.6-3
296-
return &quic.ApplicationError{
297-
Code: uint64(errH3IDError),
298-
Reason: "push stream created when no MAX_PUSH_ID has been sent",
296+
return &connectionError{
297+
code: errH3IDError,
298+
message: "push stream created when no MAX_PUSH_ID has been sent",
299+
}
300+
}
301+
302+
// abort closes the connection with an error.
303+
func (cc *ClientConn) abort(err error) {
304+
if e, ok := err.(*connectionError); ok {
305+
cc.qconn.Abort(&quic.ApplicationError{
306+
Code: uint64(e.code),
307+
Reason: e.message,
308+
})
309+
} else {
310+
cc.qconn.Abort(err)
299311
}
300312
}

0 commit comments

Comments
 (0)