From d2a52a1433c38f875c76d4015134005c11f878c8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 25 Aug 2018 22:35:03 +0700 Subject: [PATCH] implement parsing, writing and logging of CRYPTO frames --- internal/wire/crypto_frame.go | 71 +++++++++++++++++++ internal/wire/crypto_frame_test.go | 105 +++++++++++++++++++++++++++++ internal/wire/frame_parser.go | 5 ++ internal/wire/frame_parser_test.go | 13 ++++ internal/wire/log.go | 4 ++ internal/wire/log_test.go | 12 +++- 6 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 internal/wire/crypto_frame.go create mode 100644 internal/wire/crypto_frame_test.go diff --git a/internal/wire/crypto_frame.go b/internal/wire/crypto_frame.go new file mode 100644 index 000000000..264ac5f3a --- /dev/null +++ b/internal/wire/crypto_frame.go @@ -0,0 +1,71 @@ +package wire + +import ( + "bytes" + "io" + + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/utils" +) + +// A CryptoFrame is a CRYPTO frame +type CryptoFrame struct { + Offset protocol.ByteCount + Data []byte +} + +func parseCryptoFrame(r *bytes.Reader, _ protocol.VersionNumber) (*CryptoFrame, error) { + if _, err := r.ReadByte(); err != nil { + return nil, err + } + + frame := &CryptoFrame{} + offset, err := utils.ReadVarInt(r) + if err != nil { + return nil, err + } + frame.Offset = protocol.ByteCount(offset) + dataLen, err := utils.ReadVarInt(r) + if err != nil { + return nil, err + } + if dataLen > uint64(r.Len()) { + return nil, io.EOF + } + if dataLen != 0 { + frame.Data = make([]byte, dataLen) + if _, err := io.ReadFull(r, frame.Data); err != nil { + // this should never happen, since we already checked the dataLen earlier + return nil, err + } + } + return frame, nil +} + +func (f *CryptoFrame) Write(b *bytes.Buffer, _ protocol.VersionNumber) error { + b.WriteByte(0x18) + utils.WriteVarInt(b, uint64(f.Offset)) + utils.WriteVarInt(b, uint64(len(f.Data))) + b.Write(f.Data) + return nil +} + +// Length of a written frame +func (f *CryptoFrame) Length(_ protocol.VersionNumber) protocol.ByteCount { + return 1 + utils.VarIntLen(uint64(f.Offset)) + utils.VarIntLen(uint64(len(f.Data))) + protocol.ByteCount(len(f.Data)) +} + +// MaxDataLen returns the maximum data length +func (f *CryptoFrame) MaxDataLen(maxSize protocol.ByteCount) protocol.ByteCount { + // pretend that the data size will be 1 bytes + // if it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterwards + headerLen := 1 + utils.VarIntLen(uint64(f.Offset)) + 1 + if headerLen > maxSize { + return 0 + } + maxDataLen := maxSize - headerLen + if utils.VarIntLen(uint64(maxDataLen)) != 1 { + maxDataLen-- + } + return maxDataLen +} diff --git a/internal/wire/crypto_frame_test.go b/internal/wire/crypto_frame_test.go new file mode 100644 index 000000000..492ec1ce6 --- /dev/null +++ b/internal/wire/crypto_frame_test.go @@ -0,0 +1,105 @@ +package wire + +import ( + "bytes" + + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("CRYPTO frame", func() { + Context("when parsing", func() { + It("parses", func() { + data := []byte{0x18} + data = append(data, encodeVarInt(0xdecafbad)...) // offset + data = append(data, encodeVarInt(6)...) // length + data = append(data, []byte("foobar")...) + r := bytes.NewReader(data) + frame, err := parseCryptoFrame(r, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + Expect(frame.Offset).To(Equal(protocol.ByteCount(0xdecafbad))) + Expect(frame.Data).To(Equal([]byte("foobar"))) + Expect(r.Len()).To(BeZero()) + }) + + It("errors on EOFs", func() { + data := []byte{0x18} + data = append(data, encodeVarInt(0xdecafbad)...) // offset + data = append(data, encodeVarInt(6)...) // data length + data = append(data, []byte("foobar")...) + _, err := parseCryptoFrame(bytes.NewReader(data), versionIETFFrames) + Expect(err).NotTo(HaveOccurred()) + for i := range data { + _, err := parseCryptoFrame(bytes.NewReader(data[0:i]), versionIETFFrames) + Expect(err).To(HaveOccurred()) + } + }) + }) + + Context("when writing", func() { + It("writes a frame", func() { + f := &CryptoFrame{ + Offset: 0x123456, + Data: []byte("foobar"), + } + b := &bytes.Buffer{} + err := f.Write(b, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + expected := []byte{0x18} + expected = append(expected, encodeVarInt(0x123456)...) // offset + expected = append(expected, encodeVarInt(6)...) // length + expected = append(expected, []byte("foobar")...) + Expect(b.Bytes()).To(Equal(expected)) + }) + }) + + Context("max data length", func() { + const maxSize = 3000 + + It("always returns a data length such that the resulting frame has the right size", func() { + data := make([]byte, maxSize) + f := &CryptoFrame{ + Offset: 0xdeadbeef, + } + b := &bytes.Buffer{} + var frameOneByteTooSmallCounter int + for i := 1; i < maxSize; i++ { + b.Reset() + f.Data = nil + maxDataLen := f.MaxDataLen(protocol.ByteCount(i)) + if maxDataLen == 0 { // 0 means that no valid CRYTPO frame can be written + // check that writing a minimal size CRYPTO frame (i.e. with 1 byte data) is actually larger than the desired size + f.Data = []byte{0} + err := f.Write(b, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + Expect(b.Len()).To(BeNumerically(">", i)) + continue + } + f.Data = data[:int(maxDataLen)] + err := f.Write(b, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + // There's *one* pathological case, where a data length of x can be encoded into 1 byte + // but a data lengths of x+1 needs 2 bytes + // In that case, it's impossible to create a STREAM frame of the desired size + if b.Len() == i-1 { + frameOneByteTooSmallCounter++ + continue + } + Expect(b.Len()).To(Equal(i)) + } + Expect(frameOneByteTooSmallCounter).To(Equal(1)) + }) + }) + + Context("length", func() { + It("has the right length for a frame without offset and data length", func() { + f := &CryptoFrame{ + Offset: 0x1337, + Data: []byte("foobar"), + } + Expect(f.Length(versionIETFFrames)).To(Equal(1 + utils.VarIntLen(0x1337) + utils.VarIntLen(6) + 6)) + }) + }) +}) diff --git a/internal/wire/frame_parser.go b/internal/wire/frame_parser.go index 1990aca00..a44d704d6 100644 --- a/internal/wire/frame_parser.go +++ b/internal/wire/frame_parser.go @@ -101,6 +101,11 @@ func parseIETFFrame(r *bytes.Reader, typeByte byte, v protocol.VersionNumber) (F if err != nil { err = qerr.Error(qerr.InvalidAckData, err.Error()) } + case 0x18: + frame, err = parseCryptoFrame(r, v) + if err != nil { + err = qerr.Error(qerr.InvalidFrameData, err.Error()) + } default: err = qerr.Error(qerr.InvalidFrameData, fmt.Sprintf("unknown type byte 0x%x", typeByte)) } diff --git a/internal/wire/frame_parser_test.go b/internal/wire/frame_parser_test.go index 5543b04c1..e757eba98 100644 --- a/internal/wire/frame_parser_test.go +++ b/internal/wire/frame_parser_test.go @@ -328,6 +328,19 @@ var _ = Describe("Frame parsing", func() { Expect(frame.(*PathResponseFrame).Data).To(Equal([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) }) + It("unpacks CRYPTO frames", func() { + f := &CryptoFrame{ + Offset: 0x1337, + Data: []byte("lorem ipsum"), + } + err := f.Write(buf, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + frame, err := ParseNextFrame(bytes.NewReader(buf.Bytes()), nil, versionIETFFrames) + Expect(err).ToNot(HaveOccurred()) + Expect(frame).ToNot(BeNil()) + Expect(frame).To(Equal(f)) + }) + It("errors on invalid type", func() { _, err := ParseNextFrame(bytes.NewReader([]byte{0x42}), nil, versionIETFFrames) Expect(err).To(MatchError("InvalidFrameData: unknown type byte 0x42")) diff --git a/internal/wire/log.go b/internal/wire/log.go index 465e82ab9..4cd74635e 100644 --- a/internal/wire/log.go +++ b/internal/wire/log.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/utils" ) @@ -17,6 +18,9 @@ func LogFrame(logger utils.Logger, frame Frame, sent bool) { dir = "->" } switch f := frame.(type) { + case *CryptoFrame: + dataLen := protocol.ByteCount(len(f.Data)) + logger.Debugf("\t%s &wire.CryptoFrame{Offset: 0x%x, Data length: 0x%x, Offset + Data length: 0x%x}", dir, f.Offset, dataLen, f.Offset+dataLen) case *StreamFrame: logger.Debugf("\t%s &wire.StreamFrame{StreamID: %d, FinBit: %t, Offset: 0x%x, Data length: 0x%x, Offset + Data length: 0x%x}", dir, f.StreamID, f.FinBit, f.Offset, f.DataLen(), f.Offset+f.DataLen()) case *StopWaitingFrame: diff --git a/internal/wire/log_test.go b/internal/wire/log_test.go index 4b1078f85..4ecba9d40 100644 --- a/internal/wire/log_test.go +++ b/internal/wire/log_test.go @@ -46,7 +46,17 @@ var _ = Describe("Frame logging", func() { Expect(buf.Bytes()).To(ContainSubstring("\t<- &wire.RstStreamFrame{StreamID:0x0, ErrorCode:0x0, ByteOffset:0x0}\n")) }) - It("logs stream frames", func() { + It("logs CRYPTO frames", func() { + frame := &CryptoFrame{ + Offset: 0x42, + Data: make([]byte, 0x123), + } + LogFrame(logger, frame, false) + Expect(buf.Bytes()).To(ContainSubstring("\t<- &wire.CryptoFrame{Offset: 0x42, Data length: 0x123, Offset + Data length: 0x165}\n")) + + }) + + It("logs STREAM frames", func() { frame := &StreamFrame{ StreamID: 42, Offset: 0x1337,