package quic import ( "bytes" "golang.org/x/exp/rand" "github.com/refraction-networking/uquic/internal/ackhandler" "github.com/refraction-networking/uquic/internal/protocol" "github.com/refraction-networking/uquic/internal/wire" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "go.uber.org/mock/gomock" ) var _ = Describe("Framer", func() { const ( id1 = protocol.StreamID(10) id2 = protocol.StreamID(11) ) var ( framer framer stream1, stream2 *MockSendStreamI streamGetter *MockStreamGetter version protocol.Version ) BeforeEach(func() { streamGetter = NewMockStreamGetter(mockCtrl) stream1 = NewMockSendStreamI(mockCtrl) stream1.EXPECT().StreamID().Return(protocol.StreamID(5)).AnyTimes() stream2 = NewMockSendStreamI(mockCtrl) stream2.EXPECT().StreamID().Return(protocol.StreamID(6)).AnyTimes() framer = newFramer(streamGetter) }) Context("handling control frames", func() { It("adds control frames", func() { mdf := &wire.MaxDataFrame{MaximumData: 0x42} msf := &wire.MaxStreamsFrame{MaxStreamNum: 0x1337} framer.QueueControlFrame(mdf) framer.QueueControlFrame(msf) frames, length := framer.AppendControlFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(2)) fs := []wire.Frame{frames[0].Frame, frames[1].Frame} Expect(fs).To(ContainElement(mdf)) Expect(fs).To(ContainElement(msf)) Expect(length).To(Equal(mdf.Length(version) + msf.Length(version))) }) It("says if it has data", func() { Expect(framer.HasData()).To(BeFalse()) f := &wire.MaxDataFrame{MaximumData: 0x42} framer.QueueControlFrame(f) Expect(framer.HasData()).To(BeTrue()) frames, _ := framer.AppendControlFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(framer.HasData()).To(BeFalse()) }) It("appends to the slice given", func() { ping := &wire.PingFrame{} mdf := &wire.MaxDataFrame{MaximumData: 0x42} framer.QueueControlFrame(mdf) frames, length := framer.AppendControlFrames([]ackhandler.Frame{{Frame: ping}}, 1000, protocol.Version1) Expect(frames).To(HaveLen(2)) Expect(frames[0].Frame).To(Equal(ping)) Expect(frames[1].Frame).To(Equal(mdf)) Expect(length).To(Equal(mdf.Length(version))) }) It("adds the right number of frames", func() { maxSize := protocol.ByteCount(1000) bf := &wire.DataBlockedFrame{MaximumData: 0x1337} bfLen := bf.Length(version) numFrames := int(maxSize / bfLen) // max number of frames that fit into maxSize for i := 0; i < numFrames+1; i++ { framer.QueueControlFrame(bf) } frames, length := framer.AppendControlFrames(nil, maxSize, protocol.Version1) Expect(frames).To(HaveLen(numFrames)) Expect(length).To(BeNumerically(">", maxSize-bfLen)) frames, length = framer.AppendControlFrames(nil, maxSize, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(length).To(Equal(bfLen)) }) It("drops *_BLOCKED frames when 0-RTT is rejected", func() { ping := &wire.PingFrame{} ncid := &wire.NewConnectionIDFrame{ SequenceNumber: 10, ConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}), } frames := []wire.Frame{ &wire.DataBlockedFrame{MaximumData: 1337}, &wire.StreamDataBlockedFrame{StreamID: 42, MaximumStreamData: 1337}, &wire.StreamsBlockedFrame{StreamLimit: 13}, ping, ncid, } rand.Shuffle(len(frames), func(i, j int) { frames[i], frames[j] = frames[j], frames[i] }) for _, f := range frames { framer.QueueControlFrame(f) } Expect(framer.Handle0RTTRejection()).To(Succeed()) fs, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(fs).To(HaveLen(2)) Expect(length).To(Equal(ping.Length(version) + ncid.Length(version))) }) It("detects when too many frames are queued", func() { for i := 0; i < maxControlFrames-1; i++ { framer.QueueControlFrame(&wire.PingFrame{}) framer.QueueControlFrame(&wire.PingFrame{}) Expect(framer.QueuedTooManyControlFrames()).To(BeFalse()) frames, _ := framer.AppendControlFrames([]ackhandler.Frame{}, 1, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(framer.(*framerI).controlFrames).To(HaveLen(i + 1)) } framer.QueueControlFrame(&wire.PingFrame{}) Expect(framer.QueuedTooManyControlFrames()).To(BeFalse()) Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames)) framer.QueueControlFrame(&wire.PingFrame{}) Expect(framer.QueuedTooManyControlFrames()).To(BeTrue()) Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames)) }) }) Context("handling PATH_RESPONSE frames", func() { It("packs a single PATH_RESPONSE per packet", func() { f1 := &wire.PathResponseFrame{Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}} f2 := &wire.PathResponseFrame{Data: [8]byte{2, 3, 4, 5, 6, 7, 8, 9}} cf1 := &wire.DataBlockedFrame{MaximumData: 1337} cf2 := &wire.HandshakeDoneFrame{} framer.QueueControlFrame(f1) framer.QueueControlFrame(f2) framer.QueueControlFrame(cf1) framer.QueueControlFrame(cf2) // the first packet should contain a single PATH_RESPONSE frame, but all the other control frames Expect(framer.HasData()).To(BeTrue()) frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(frames).To(HaveLen(3)) Expect(frames[0].Frame).To(Equal(f1)) Expect([]wire.Frame{frames[1].Frame, frames[2].Frame}).To(ContainElement(cf1)) Expect([]wire.Frame{frames[1].Frame, frames[2].Frame}).To(ContainElement(cf2)) Expect(length).To(Equal(f1.Length(protocol.Version1) + cf1.Length(protocol.Version1) + cf2.Length(protocol.Version1))) // the second packet should contain the other PATH_RESPONSE frame Expect(framer.HasData()).To(BeTrue()) frames, length = framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f2)) Expect(length).To(Equal(f2.Length(protocol.Version1))) Expect(framer.HasData()).To(BeFalse()) }) It("limits the number of queued PATH_RESPONSE frames", func() { var pathResponses []*wire.PathResponseFrame for i := 0; i < 2*maxPathResponses; i++ { var f wire.PathResponseFrame rand.Read(f.Data[:]) pathResponses = append(pathResponses, &f) framer.QueueControlFrame(&f) } for i := 0; i < maxPathResponses; i++ { Expect(framer.HasData()).To(BeTrue()) frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(pathResponses[i])) Expect(length).To(Equal(pathResponses[i].Length(protocol.Version1))) } Expect(framer.HasData()).To(BeFalse()) frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(frames).To(BeEmpty()) Expect(length).To(BeZero()) }) }) Context("popping STREAM frames", func() { It("returns nil when popping an empty framer", func() { Expect(framer.AppendStreamFrames(nil, 1000, protocol.Version1)).To(BeEmpty()) }) It("returns STREAM frames", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) f := &wire.StreamFrame{ StreamID: id1, Data: []byte("foobar"), Offset: 42, DataLenPresent: true, } stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) framer.AddActiveStream(id1) fs, length := framer.AppendStreamFrames(nil, 1000, protocol.Version1) Expect(fs).To(HaveLen(1)) Expect(fs[0].Frame.DataLenPresent).To(BeFalse()) Expect(length).To(Equal(f.Length(version))) }) It("says if it has data", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil).Times(2) Expect(framer.HasData()).To(BeFalse()) framer.AddActiveStream(id1) Expect(framer.HasData()).To(BeTrue()) f1 := &wire.StreamFrame{StreamID: id1, Data: []byte("foo")} f2 := &wire.StreamFrame{StreamID: id1, Data: []byte("bar")} stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f1}, true, true) stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f2}, true, false) frames, _ := framer.AppendStreamFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f1)) Expect(framer.HasData()).To(BeTrue()) frames, _ = framer.AppendStreamFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f2)) Expect(framer.HasData()).To(BeFalse()) }) It("appends to a frame slice", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) f := &wire.StreamFrame{ StreamID: id1, Data: []byte("foobar"), DataLenPresent: true, } stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) framer.AddActiveStream(id1) f0 := ackhandler.StreamFrame{Frame: &wire.StreamFrame{StreamID: 9999}} frames := []ackhandler.StreamFrame{f0} fs, length := framer.AppendStreamFrames(frames, 1000, protocol.Version1) Expect(fs).To(HaveLen(2)) Expect(fs[0]).To(Equal(f0)) Expect(fs[1].Frame.Data).To(Equal([]byte("foobar"))) Expect(fs[1].Frame.DataLenPresent).To(BeFalse()) Expect(length).To(Equal(f.Length(version))) }) It("skips a stream that was reported active, but was completed shortly after", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(nil, nil) streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil) f := &wire.StreamFrame{ StreamID: id2, Data: []byte("foobar"), DataLenPresent: true, } stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) framer.AddActiveStream(id1) framer.AddActiveStream(id2) frames, _ := framer.AppendStreamFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f)) }) It("skips a stream that was reported active, but doesn't have any data", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil) f := &wire.StreamFrame{ StreamID: id2, Data: []byte("foobar"), DataLenPresent: true, } stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{}, false, false) stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) framer.AddActiveStream(id1) framer.AddActiveStream(id2) frames, _ := framer.AppendStreamFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f)) }) It("pops from a stream multiple times, if it has enough data", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil).Times(2) f1 := &wire.StreamFrame{StreamID: id1, Data: []byte("foobar")} f2 := &wire.StreamFrame{StreamID: id1, Data: []byte("foobaz")} stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f1}, true, true) stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f2}, true, false) framer.AddActiveStream(id1) // only add it once frames, _ := framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f1)) frames, _ = framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f2)) // no further calls to popStreamFrame, after popStreamFrame said there's no more data frames, _ = framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) Expect(frames).To(BeNil()) }) It("re-queues a stream at the end, if it has enough data", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil).Times(2) streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil) f11 := &wire.StreamFrame{StreamID: id1, Data: []byte("foobar")} f12 := &wire.StreamFrame{StreamID: id1, Data: []byte("foobaz")} f2 := &wire.StreamFrame{StreamID: id2, Data: []byte("raboof")} stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f11}, true, true) stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f12}, true, false) stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f2}, true, false) framer.AddActiveStream(id1) // only add it once framer.AddActiveStream(id2) // first a frame from stream 1 frames, _ := framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f11)) // then a frame from stream 2 frames, _ = framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f2)) // then another frame from stream 1 frames, _ = framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) Expect(frames).To(HaveLen(1)) Expect(frames[0].Frame).To(Equal(f12)) }) It("only dequeues data from each stream once per packet", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil) f1 := &wire.StreamFrame{StreamID: id1, Data: []byte("foobar")} f2 := &wire.StreamFrame{StreamID: id2, Data: []byte("raboof")} // both streams have more data, and will be re-queued stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f1}, true, true) stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f2}, true, true) framer.AddActiveStream(id1) framer.AddActiveStream(id2) frames, length := framer.AppendStreamFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(2)) Expect(frames[0].Frame).To(Equal(f1)) Expect(frames[1].Frame).To(Equal(f2)) Expect(length).To(Equal(f1.Length(version) + f2.Length(version))) }) It("returns multiple normal frames in the order they were reported active", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil) f1 := &wire.StreamFrame{Data: []byte("foobar")} f2 := &wire.StreamFrame{Data: []byte("foobaz")} stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f1}, true, false) stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f2}, true, false) framer.AddActiveStream(id2) framer.AddActiveStream(id1) frames, _ := framer.AppendStreamFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(2)) Expect(frames[0].Frame).To(Equal(f2)) Expect(frames[1].Frame).To(Equal(f1)) }) It("only asks a stream for data once, even if it was reported active multiple times", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) f := &wire.StreamFrame{Data: []byte("foobar")} stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) // only one call to this function framer.AddActiveStream(id1) framer.AddActiveStream(id1) frames, _ := framer.AppendStreamFrames(nil, 1000, protocol.Version1) Expect(frames).To(HaveLen(1)) }) It("does not pop empty frames", func() { fs, length := framer.AppendStreamFrames(nil, 500, protocol.Version1) Expect(fs).To(BeEmpty()) Expect(length).To(BeZero()) }) It("pops maximum size STREAM frames", func() { for i := protocol.MinStreamFrameSize; i < 2000; i++ { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) { f := &wire.StreamFrame{ StreamID: id1, DataLenPresent: true, } f.Data = make([]byte, f.MaxDataLen(size, v)) Expect(f.Length(version)).To(Equal(size)) return ackhandler.StreamFrame{Frame: f}, true, false }) framer.AddActiveStream(id1) frames, _ := framer.AppendStreamFrames(nil, i, protocol.Version1) Expect(frames).To(HaveLen(1)) f := frames[0].Frame Expect(f.DataLenPresent).To(BeFalse()) Expect(f.Length(version)).To(Equal(i)) } }) It("pops multiple STREAM frames", func() { for i := 2 * protocol.MinStreamFrameSize; i < 2000; i++ { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil) stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) { f := &wire.StreamFrame{ StreamID: id2, DataLenPresent: true, } f.Data = make([]byte, f.MaxDataLen(protocol.MinStreamFrameSize, v)) return ackhandler.StreamFrame{Frame: f}, true, false }) stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) { f := &wire.StreamFrame{ StreamID: id2, DataLenPresent: true, } f.Data = make([]byte, f.MaxDataLen(size, v)) Expect(f.Length(version)).To(Equal(size)) return ackhandler.StreamFrame{Frame: f}, true, false }) framer.AddActiveStream(id1) framer.AddActiveStream(id2) frames, _ := framer.AppendStreamFrames(nil, i, protocol.Version1) Expect(frames).To(HaveLen(2)) f1 := frames[0].Frame f2 := frames[1].Frame Expect(f1.DataLenPresent).To(BeTrue()) Expect(f2.DataLenPresent).To(BeFalse()) Expect(f1.Length(version) + f2.Length(version)).To(Equal(i)) } }) It("pops frames that when asked for the the minimum STREAM frame size", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) f := &wire.StreamFrame{Data: []byte("foobar")} stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) framer.AddActiveStream(id1) framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize, protocol.Version1) }) It("does not pop frames smaller than the minimum size", func() { // don't expect a call to PopStreamFrame() framer.AppendStreamFrames(nil, protocol.MinStreamFrameSize-1, protocol.Version1) }) It("stops iterating when the remaining size is smaller than the minimum STREAM frame size", func() { streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil) // pop a frame such that the remaining size is one byte less than the minimum STREAM frame size f := &wire.StreamFrame{ StreamID: id1, Data: bytes.Repeat([]byte("f"), int(500-protocol.MinStreamFrameSize)), DataLenPresent: true, } stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).Return(ackhandler.StreamFrame{Frame: f}, true, false) framer.AddActiveStream(id1) fs, length := framer.AppendStreamFrames(nil, 500, protocol.Version1) Expect(fs).To(HaveLen(1)) Expect(fs[0].Frame).To(Equal(f)) Expect(length).To(Equal(f.Length(version))) }) It("drops all STREAM frames when 0-RTT is rejected", func() { framer.AddActiveStream(id1) Expect(framer.Handle0RTTRejection()).To(Succeed()) fs, length := framer.AppendStreamFrames(nil, protocol.MaxByteCount, protocol.Version1) Expect(fs).To(BeEmpty()) Expect(length).To(BeZero()) }) }) })