diff --git a/internal/qerr/error_codes.go b/internal/qerr/error_codes.go index f5a9de5e3..426133961 100644 --- a/internal/qerr/error_codes.go +++ b/internal/qerr/error_codes.go @@ -31,11 +31,20 @@ func (e ErrorCode) isCryptoError() bool { func (e ErrorCode) Error() string { if e.isCryptoError() { - return fmt.Sprintf("%s: %s", e.String(), qtls.Alert(e-0x100).Error()) + return fmt.Sprintf("%s: %s", e.String(), e.Message()) } return e.String() } +// Message is a description of the error. +// It only returns a non-empty string for crypto errors. +func (e ErrorCode) Message() string { + if !e.isCryptoError() { + return "" + } + return qtls.Alert(e - 0x100).Error() +} + func (e ErrorCode) String() string { switch e { case NoError: diff --git a/internal/qerr/quic_error.go b/internal/qerr/quic_error.go index 66e8960c5..07148c367 100644 --- a/internal/qerr/quic_error.go +++ b/internal/qerr/quic_error.go @@ -8,6 +8,7 @@ import ( // A QuicError consists of an error code plus a error reason type QuicError struct { ErrorCode ErrorCode + FrameType uint64 // only valid if this not an application error ErrorMessage string isTimeout bool isApplicationError bool @@ -23,6 +24,15 @@ func Error(errorCode ErrorCode, errorMessage string) *QuicError { } } +// ErrorWithFrameType creates a new QuicError instance for a specific frame type +func ErrorWithFrameType(errorCode ErrorCode, frameType uint64, errorMessage string) *QuicError { + return &QuicError{ + ErrorCode: errorCode, + FrameType: frameType, + ErrorMessage: errorMessage, + } +} + // TimeoutError creates a new QuicError instance for a timeout error func TimeoutError(errorMessage string) *QuicError { return &QuicError{ @@ -55,10 +65,18 @@ func (e *QuicError) Error() string { } return fmt.Sprintf("Application error %#x: %s", uint64(e.ErrorCode), e.ErrorMessage) } - if len(e.ErrorMessage) == 0 { - return e.ErrorCode.Error() + str := e.ErrorCode.String() + if e.FrameType != 0 { + str += fmt.Sprintf(" (frame type: %#x)", e.FrameType) } - return fmt.Sprintf("%s: %s", e.ErrorCode.String(), e.ErrorMessage) + msg := e.ErrorMessage + if len(msg) == 0 { + msg = e.ErrorCode.Message() + } + if len(msg) == 0 { + return str + } + return str + ": " + msg } // IsCryptoError says if this error is a crypto error diff --git a/internal/qerr/quic_error_test.go b/internal/qerr/quic_error_test.go index 9c48487e3..162ff2adc 100644 --- a/internal/qerr/quic_error_test.go +++ b/internal/qerr/quic_error_test.go @@ -20,6 +20,16 @@ var _ = Describe("QUIC Transport Errors", func() { Expect(err.Error()).To(Equal("FLOW_CONTROL_ERROR")) }) + It("includes the frame type, for errors without a message", func() { + err := ErrorWithFrameType(FlowControlError, 0x1337, "") + Expect(err.Error()).To(Equal("FLOW_CONTROL_ERROR (frame type: 0x1337)")) + }) + + It("includes the frame type, for errors with a message", func() { + err := ErrorWithFrameType(FlowControlError, 0x1337, "foobar") + Expect(err.Error()).To(Equal("FLOW_CONTROL_ERROR (frame type: 0x1337): foobar")) + }) + It("has a string representation for timeout errors", func() { err := TimeoutError("foobar") Expect(err.Timeout()).To(BeTrue())