diff --git a/internal/wire/connection_close_frame.go b/internal/wire/connection_close_frame.go index 473f3dce2..fead57a19 100644 --- a/internal/wire/connection_close_frame.go +++ b/internal/wire/connection_close_frame.go @@ -2,39 +2,44 @@ package wire import ( "bytes" - "errors" "io" - "math" "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/utils" "github.com/lucas-clemente/quic-go/qerr" ) -// A ConnectionCloseFrame in QUIC +// A ConnectionCloseFrame is a CONNECTION_CLOSE frame type ConnectionCloseFrame struct { - ErrorCode qerr.ErrorCode - ReasonPhrase string + IsApplicationError bool + ErrorCode qerr.ErrorCode + ReasonPhrase string } // parseConnectionCloseFrame reads a CONNECTION_CLOSE frame func parseConnectionCloseFrame(r *bytes.Reader, version protocol.VersionNumber) (*ConnectionCloseFrame, error) { - if _, err := r.ReadByte(); err != nil { // read the TypeByte + typeByte, err := r.ReadByte() + if err != nil { return nil, err } - var errorCode qerr.ErrorCode - var reasonPhraseLen uint64 + f := &ConnectionCloseFrame{IsApplicationError: typeByte == 0x03} ec, err := utils.BigEndian.ReadUint16(r) if err != nil { return nil, err } - errorCode = qerr.ErrorCode(ec) + f.ErrorCode = qerr.ErrorCode(ec) + // read the Frame Type, if this is not an application error + if !f.IsApplicationError { + if _, err := utils.ReadVarInt(r); err != nil { + return nil, err + } + } + var reasonPhraseLen uint64 reasonPhraseLen, err = utils.ReadVarInt(r) if err != nil { return nil, err } - // shortcut to prevent the unnecessary allocation of dataLen bytes // if the dataLen is larger than the remaining length of the packet // reading the whole reason phrase would result in EOF when attempting to READ @@ -47,29 +52,32 @@ func parseConnectionCloseFrame(r *bytes.Reader, version protocol.VersionNumber) // this should never happen, since we already checked the reasonPhraseLen earlier return nil, err } - - return &ConnectionCloseFrame{ - ErrorCode: errorCode, - ReasonPhrase: string(reasonPhrase), - }, nil + f.ReasonPhrase = string(reasonPhrase) + return f, nil } // Length of a written frame func (f *ConnectionCloseFrame) Length(version protocol.VersionNumber) protocol.ByteCount { - return 1 + 2 + utils.VarIntLen(uint64(len(f.ReasonPhrase))) + protocol.ByteCount(len(f.ReasonPhrase)) + length := 1 + 2 + utils.VarIntLen(uint64(len(f.ReasonPhrase))) + protocol.ByteCount(len(f.ReasonPhrase)) + if !f.IsApplicationError { + length++ // for the frame type + } + return length } // Write writes an CONNECTION_CLOSE frame. func (f *ConnectionCloseFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - b.WriteByte(0x02) - - if len(f.ReasonPhrase) > math.MaxUint16 { - return errors.New("ConnectionFrame: ReasonPhrase too long") + if f.IsApplicationError { + b.WriteByte(0x03) + } else { + b.WriteByte(0x02) } utils.BigEndian.WriteUint16(b, uint16(f.ErrorCode)) + if !f.IsApplicationError { + utils.WriteVarInt(b, 0) + } utils.WriteVarInt(b, uint64(len(f.ReasonPhrase))) b.WriteString(f.ReasonPhrase) - return nil } diff --git a/internal/wire/connection_close_frame_test.go b/internal/wire/connection_close_frame_test.go index c21980915..9f79ea6cf 100644 --- a/internal/wire/connection_close_frame_test.go +++ b/internal/wire/connection_close_frame_test.go @@ -12,22 +12,38 @@ import ( var _ = Describe("CONNECTION_CLOSE Frame", func() { Context("when parsing", func() { - It("accepts sample frame", func() { + It("accepts sample frame containing a QUIC error code", func() { + reason := "No recent network activity." data := []byte{0x2, 0x0, 0x19} - data = append(data, encodeVarInt(0x1b)...) // reason phrase length - data = append(data, []byte{ - 'N', 'o', ' ', 'r', 'e', 'c', 'e', 'n', 't', ' ', 'n', 'e', 't', 'w', 'o', 'r', 'k', ' ', 'a', 'c', 't', 'i', 'v', 'i', 't', 'y', '.', - }...) + data = append(data, encodeVarInt(0x1337)...) // frame type + data = append(data, encodeVarInt(uint64(len(reason)))...) // reason phrase length + data = append(data, []byte(reason)...) b := bytes.NewReader(data) frame, err := parseConnectionCloseFrame(b, versionIETFFrames) Expect(err).ToNot(HaveOccurred()) + Expect(frame.IsApplicationError).To(BeFalse()) Expect(frame.ErrorCode).To(Equal(qerr.ErrorCode(0x19))) - Expect(frame.ReasonPhrase).To(Equal("No recent network activity.")) + Expect(frame.ReasonPhrase).To(Equal(reason)) + Expect(b.Len()).To(BeZero()) + }) + + It("accepts sample frame containing an application error code", func() { + reason := "The application messed things up." + data := []byte{0x3, 0x0, 0x19} + data = append(data, encodeVarInt(uint64(len(reason)))...) // reason phrase length + data = append(data, reason...) + b := bytes.NewReader(data) + frame, err := parseConnectionCloseFrame(b, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + Expect(frame.IsApplicationError).To(BeTrue()) + Expect(frame.ErrorCode).To(Equal(qerr.ErrorCode(0x19))) + Expect(frame.ReasonPhrase).To(Equal(reason)) Expect(b.Len()).To(BeZero()) }) It("rejects long reason phrases", func() { data := []byte{0x2, 0xca, 0xfe} + data = append(data, encodeVarInt(0x42)...) // frame type data = append(data, encodeVarInt(0xffff)...) // reason phrase length b := bytes.NewReader(data) _, err := parseConnectionCloseFrame(b, versionIETFFrames) @@ -35,11 +51,11 @@ var _ = Describe("CONNECTION_CLOSE Frame", func() { }) It("errors on EOFs", func() { + reason := "No recent network activity." data := []byte{0x2, 0x0, 0x19} - data = append(data, encodeVarInt(0x1b)...) // reason phrase length - data = append(data, []byte{ - 'N', 'o', ' ', 'r', 'e', 'c', 'e', 'n', 't', ' ', 'n', 'e', 't', 'w', 'o', 'r', 'k', ' ', 'a', 'c', 't', 'i', 'v', 'i', 't', 'y', '.', - }...) + data = append(data, encodeVarInt(0x1337)...) // frame type + data = append(data, encodeVarInt(uint64(len(reason)))...) // reason phrase length + data = append(data, []byte(reason)...) _, err := parseConnectionCloseFrame(bytes.NewReader(data), versionIETFFrames) Expect(err).NotTo(HaveOccurred()) for i := range data { @@ -50,6 +66,7 @@ var _ = Describe("CONNECTION_CLOSE Frame", func() { It("parses a frame without a reason phrase", func() { data := []byte{0x2, 0xca, 0xfe} + data = append(data, encodeVarInt(0x42)...) // frame type data = append(data, encodeVarInt(0)...) b := bytes.NewReader(data) frame, err := parseConnectionCloseFrame(b, versionIETFFrames) @@ -68,7 +85,8 @@ var _ = Describe("CONNECTION_CLOSE Frame", func() { err := frame.Write(b, versionIETFFrames) Expect(err).ToNot(HaveOccurred()) expected := []byte{0x2, 0xbe, 0xef} - expected = append(expected, encodeVarInt(0)...) + expected = append(expected, encodeVarInt(0)...) // frame type + expected = append(expected, encodeVarInt(0)...) // reason phrase length Expect(b.Bytes()).To(Equal(expected)) }) @@ -81,12 +99,28 @@ var _ = Describe("CONNECTION_CLOSE Frame", func() { err := frame.Write(b, versionIETFFrames) Expect(err).ToNot(HaveOccurred()) expected := []byte{0x2, 0xde, 0xad} - expected = append(expected, encodeVarInt(6)...) - expected = append(expected, []byte{'f', 'o', 'o', 'b', 'a', 'r'}...) + expected = append(expected, encodeVarInt(0)...) // frame type + expected = append(expected, encodeVarInt(6)...) // reason phrase length + expected = append(expected, []byte("foobar")...) Expect(b.Bytes()).To(Equal(expected)) }) - It("has proper min length", func() { + It("writes a frame with an application error code", func() { + b := &bytes.Buffer{} + frame := &ConnectionCloseFrame{ + IsApplicationError: true, + ErrorCode: 0xdead, + ReasonPhrase: "foobar", + } + err := frame.Write(b, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + expected := []byte{0x3, 0xde, 0xad} + expected = append(expected, encodeVarInt(6)...) // reason phrase length + expected = append(expected, []byte("foobar")...) + Expect(b.Bytes()).To(Equal(expected)) + }) + + It("has proper min length, for a frame containing a QUIC error code", func() { b := &bytes.Buffer{} f := &ConnectionCloseFrame{ ErrorCode: 0xcafe, @@ -96,5 +130,17 @@ var _ = Describe("CONNECTION_CLOSE Frame", func() { Expect(err).ToNot(HaveOccurred()) Expect(f.Length(versionIETFFrames)).To(Equal(protocol.ByteCount(b.Len()))) }) + + It("has proper min length, for a frame containing an application error code", func() { + b := &bytes.Buffer{} + f := &ConnectionCloseFrame{ + IsApplicationError: true, + ErrorCode: 0xcafe, + ReasonPhrase: "foobar", + } + err := f.Write(b, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + Expect(f.Length(versionIETFFrames)).To(Equal(protocol.ByteCount(b.Len()))) + }) }) }) diff --git a/internal/wire/frame_parser.go b/internal/wire/frame_parser.go index ed69c01f7..eb87dc6fe 100644 --- a/internal/wire/frame_parser.go +++ b/internal/wire/frame_parser.go @@ -40,7 +40,7 @@ func parseFrame(r *bytes.Reader, typeByte byte, v protocol.VersionNumber) (Frame if err != nil { err = qerr.Error(qerr.InvalidRstStreamData, err.Error()) } - case 0x2: + case 0x2, 0x3: frame, err = parseConnectionCloseFrame(r, v) if err != nil { err = qerr.Error(qerr.InvalidConnectionCloseData, err.Error()) diff --git a/internal/wire/frame_parser_test.go b/internal/wire/frame_parser_test.go index 9e771dd44..ae9da6a96 100644 --- a/internal/wire/frame_parser_test.go +++ b/internal/wire/frame_parser_test.go @@ -51,7 +51,7 @@ var _ = Describe("Frame parsing", func() { Expect(frame).To(Equal(f)) }) - It("unpacks CONNECTION_CLOSE frames", func() { + It("unpacks CONNECTION_CLOSE frames containing QUIC error codes", func() { f := &ConnectionCloseFrame{ReasonPhrase: "foo"} err := f.Write(buf, versionIETFFrames) Expect(err).ToNot(HaveOccurred()) @@ -60,6 +60,18 @@ var _ = Describe("Frame parsing", func() { Expect(frame).To(Equal(f)) }) + It("unpacks CONNECTION_CLOSE frames containing application error codes", func() { + f := &ConnectionCloseFrame{ + IsApplicationError: true, + ReasonPhrase: "foo", + } + err := f.Write(buf, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + frame, err := ParseNextFrame(bytes.NewReader(buf.Bytes()), nil, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + Expect(frame).To(Equal(f)) + }) + It("unpacks MAX_DATA frames", func() { f := &MaxDataFrame{ ByteOffset: 0xcafe,