mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 20:57:36 +03:00
When a FIN is dequeued from the stream by the streamFramer, it is guaranteed to be sent out. There's no need to explicitely signal that to the stream.
350 lines
12 KiB
Go
350 lines
12 KiB
Go
package quic
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/lucas-clemente/quic-go/internal/mocks"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
"github.com/lucas-clemente/quic-go/internal/wire"
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Stream Framer", func() {
|
|
const (
|
|
id1 = protocol.StreamID(10)
|
|
id2 = protocol.StreamID(11)
|
|
)
|
|
|
|
var (
|
|
retransmittedFrame1, retransmittedFrame2 *wire.StreamFrame
|
|
framer *streamFramer
|
|
streamsMap *streamsMap
|
|
stream1, stream2 *mocks.MockStreamI
|
|
connFC *mocks.MockConnectionFlowController
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
retransmittedFrame1 = &wire.StreamFrame{
|
|
StreamID: 5,
|
|
Data: []byte{0x13, 0x37},
|
|
}
|
|
retransmittedFrame2 = &wire.StreamFrame{
|
|
StreamID: 6,
|
|
Data: []byte{0xDE, 0xCA, 0xFB, 0xAD},
|
|
}
|
|
|
|
stream1 = mocks.NewMockStreamI(mockCtrl)
|
|
stream1.EXPECT().StreamID().Return(protocol.StreamID(5)).AnyTimes()
|
|
stream2 = mocks.NewMockStreamI(mockCtrl)
|
|
stream2.EXPECT().StreamID().Return(protocol.StreamID(6)).AnyTimes()
|
|
|
|
streamsMap = newStreamsMap(nil, protocol.PerspectiveServer, versionGQUICFrames)
|
|
streamsMap.putStream(stream1)
|
|
streamsMap.putStream(stream2)
|
|
|
|
connFC = mocks.NewMockConnectionFlowController(mockCtrl)
|
|
framer = newStreamFramer(nil, streamsMap, connFC, versionGQUICFrames)
|
|
})
|
|
|
|
setNoData := func(str *mocks.MockStreamI) {
|
|
str.EXPECT().HasDataForWriting().Return(false).AnyTimes()
|
|
str.EXPECT().GetDataForWriting(gomock.Any()).Return(nil, false).AnyTimes()
|
|
str.EXPECT().GetWriteOffset().AnyTimes()
|
|
}
|
|
|
|
It("says if it has retransmissions", func() {
|
|
Expect(framer.HasFramesForRetransmission()).To(BeFalse())
|
|
framer.AddFrameForRetransmission(retransmittedFrame1)
|
|
Expect(framer.HasFramesForRetransmission()).To(BeTrue())
|
|
})
|
|
|
|
It("sets the DataLenPresent for dequeued retransmitted frames", func() {
|
|
setNoData(stream1)
|
|
setNoData(stream2)
|
|
framer.AddFrameForRetransmission(retransmittedFrame1)
|
|
fs := framer.PopStreamFrames(protocol.MaxByteCount)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(fs[0].DataLenPresent).To(BeTrue())
|
|
})
|
|
|
|
It("sets the DataLenPresent for dequeued normal frames", func() {
|
|
connFC.EXPECT().IsBlocked()
|
|
setNoData(stream2)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobar"), false)
|
|
stream1.EXPECT().IsFlowControlBlocked()
|
|
fs := framer.PopStreamFrames(protocol.MaxByteCount)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(fs[0].DataLenPresent).To(BeTrue())
|
|
})
|
|
|
|
Context("Popping", func() {
|
|
BeforeEach(func() {
|
|
// nothing is blocked here
|
|
connFC.EXPECT().IsBlocked().AnyTimes()
|
|
stream1.EXPECT().IsFlowControlBlocked().Return(false).AnyTimes()
|
|
stream2.EXPECT().IsFlowControlBlocked().Return(false).AnyTimes()
|
|
})
|
|
|
|
It("returns nil when popping an empty framer", func() {
|
|
setNoData(stream1)
|
|
setNoData(stream2)
|
|
Expect(framer.PopStreamFrames(1000)).To(BeEmpty())
|
|
})
|
|
|
|
It("pops frames for retransmission", func() {
|
|
setNoData(stream1)
|
|
setNoData(stream2)
|
|
framer.AddFrameForRetransmission(retransmittedFrame1)
|
|
framer.AddFrameForRetransmission(retransmittedFrame2)
|
|
fs := framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(2))
|
|
Expect(fs[0]).To(Equal(retransmittedFrame1))
|
|
Expect(fs[1]).To(Equal(retransmittedFrame2))
|
|
Expect(framer.PopStreamFrames(1000)).To(BeEmpty())
|
|
})
|
|
|
|
It("returns normal frames", func() {
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobar"), false)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
setNoData(stream2)
|
|
fs := framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(fs[0].StreamID).To(Equal(stream1.StreamID()))
|
|
Expect(fs[0].Data).To(Equal([]byte("foobar")))
|
|
Expect(fs[0].FinBit).To(BeFalse())
|
|
})
|
|
|
|
It("returns multiple normal frames", func() {
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobar"), false)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
stream2.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobaz"), false)
|
|
stream2.EXPECT().HasDataForWriting().Return(true)
|
|
stream2.EXPECT().GetWriteOffset()
|
|
fs := framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(2))
|
|
// Swap if we dequeued in other order
|
|
if fs[0].StreamID != stream1.StreamID() {
|
|
fs[0], fs[1] = fs[1], fs[0]
|
|
}
|
|
Expect(fs[0].StreamID).To(Equal(stream1.StreamID()))
|
|
Expect(fs[0].Data).To(Equal([]byte("foobar")))
|
|
Expect(fs[1].StreamID).To(Equal(stream2.StreamID()))
|
|
Expect(fs[1].Data).To(Equal([]byte("foobaz")))
|
|
})
|
|
|
|
It("returns retransmission frames before normal frames", func() {
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobar"), false)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
setNoData(stream2)
|
|
framer.AddFrameForRetransmission(retransmittedFrame1)
|
|
fs := framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(2))
|
|
Expect(fs[0]).To(Equal(retransmittedFrame1))
|
|
Expect(fs[1].StreamID).To(Equal(stream1.StreamID()))
|
|
})
|
|
|
|
It("does not pop empty frames", func() {
|
|
stream1.EXPECT().HasDataForWriting().Return(false)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
setNoData(stream2)
|
|
fs := framer.PopStreamFrames(5)
|
|
Expect(fs).To(BeEmpty())
|
|
})
|
|
|
|
It("uses the round-robin scheduling", func() {
|
|
streamFrameHeaderLen := protocol.ByteCount(4)
|
|
stream1.EXPECT().GetDataForWriting(10-streamFrameHeaderLen).Return(bytes.Repeat([]byte("f"), int(10-streamFrameHeaderLen)), false)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
stream2.EXPECT().GetDataForWriting(protocol.ByteCount(10-streamFrameHeaderLen)).Return(bytes.Repeat([]byte("e"), int(10-streamFrameHeaderLen)), false)
|
|
stream2.EXPECT().HasDataForWriting().Return(true)
|
|
stream2.EXPECT().GetWriteOffset()
|
|
fs := framer.PopStreamFrames(10)
|
|
Expect(fs).To(HaveLen(1))
|
|
// it doesn't matter here if this data is from stream1 or from stream2...
|
|
firstStreamID := fs[0].StreamID
|
|
fs = framer.PopStreamFrames(10)
|
|
Expect(fs).To(HaveLen(1))
|
|
// ... but the data popped this time has to be from the other stream
|
|
Expect(fs[0].StreamID).ToNot(Equal(firstStreamID))
|
|
})
|
|
|
|
Context("splitting of frames", func() {
|
|
It("splits off nothing", func() {
|
|
f := &wire.StreamFrame{
|
|
StreamID: 1,
|
|
Data: []byte("bar"),
|
|
Offset: 3,
|
|
}
|
|
Expect(maybeSplitOffFrame(f, 1000)).To(BeNil())
|
|
Expect(f.Offset).To(Equal(protocol.ByteCount(3)))
|
|
Expect(f.Data).To(Equal([]byte("bar")))
|
|
})
|
|
|
|
It("splits off initial frame", func() {
|
|
f := &wire.StreamFrame{
|
|
StreamID: 1,
|
|
Data: []byte("foobar"),
|
|
DataLenPresent: true,
|
|
Offset: 3,
|
|
FinBit: true,
|
|
}
|
|
previous := maybeSplitOffFrame(f, 3)
|
|
Expect(previous).ToNot(BeNil())
|
|
Expect(previous.StreamID).To(Equal(protocol.StreamID(1)))
|
|
Expect(previous.Data).To(Equal([]byte("foo")))
|
|
Expect(previous.DataLenPresent).To(BeTrue())
|
|
Expect(previous.Offset).To(Equal(protocol.ByteCount(3)))
|
|
Expect(previous.FinBit).To(BeFalse())
|
|
Expect(f.StreamID).To(Equal(protocol.StreamID(1)))
|
|
Expect(f.Data).To(Equal([]byte("bar")))
|
|
Expect(f.DataLenPresent).To(BeTrue())
|
|
Expect(f.Offset).To(Equal(protocol.ByteCount(6)))
|
|
Expect(f.FinBit).To(BeTrue())
|
|
})
|
|
|
|
It("splits a frame", func() {
|
|
setNoData(stream1)
|
|
setNoData(stream2)
|
|
framer.AddFrameForRetransmission(retransmittedFrame2)
|
|
origlen := retransmittedFrame2.DataLen()
|
|
fs := framer.PopStreamFrames(6)
|
|
Expect(fs).To(HaveLen(1))
|
|
minLength, _ := fs[0].MinLength(framer.version)
|
|
Expect(minLength + fs[0].DataLen()).To(Equal(protocol.ByteCount(6)))
|
|
Expect(framer.retransmissionQueue[0].Data).To(HaveLen(int(origlen - fs[0].DataLen())))
|
|
Expect(framer.retransmissionQueue[0].Offset).To(Equal(fs[0].DataLen()))
|
|
})
|
|
|
|
It("never returns an empty stream frame", func() {
|
|
// this one frame will be split off from again and again in this test. Therefore, it has to be large enough (checked again at the end)
|
|
origFrame := &wire.StreamFrame{
|
|
StreamID: 5,
|
|
Offset: 1,
|
|
FinBit: false,
|
|
Data: bytes.Repeat([]byte{'f'}, 30*30),
|
|
}
|
|
framer.AddFrameForRetransmission(origFrame)
|
|
|
|
minFrameDataLen := protocol.MaxPacketSize
|
|
|
|
for i := 0; i < 30; i++ {
|
|
frames, currentLen := framer.maybePopFramesForRetransmission(protocol.ByteCount(i))
|
|
if len(frames) == 0 {
|
|
Expect(currentLen).To(BeZero())
|
|
} else {
|
|
Expect(frames).To(HaveLen(1))
|
|
Expect(currentLen).ToNot(BeZero())
|
|
dataLen := frames[0].DataLen()
|
|
Expect(dataLen).ToNot(BeZero())
|
|
if dataLen < minFrameDataLen {
|
|
minFrameDataLen = dataLen
|
|
}
|
|
}
|
|
}
|
|
Expect(framer.retransmissionQueue).To(HaveLen(1)) // check that origFrame was large enough for this test and didn't get used up completely
|
|
Expect(minFrameDataLen).To(Equal(protocol.ByteCount(1)))
|
|
})
|
|
|
|
It("only removes a frame from the framer after returning all split parts", func() {
|
|
setNoData(stream1)
|
|
setNoData(stream2)
|
|
framer.AddFrameForRetransmission(retransmittedFrame2)
|
|
fs := framer.PopStreamFrames(6)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(framer.retransmissionQueue).ToNot(BeEmpty())
|
|
fs = framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(framer.retransmissionQueue).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("sending FINs", func() {
|
|
It("sends FINs when streams are closed", func() {
|
|
offset := protocol.ByteCount(42)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return(nil, true)
|
|
stream1.EXPECT().GetWriteOffset().Return(offset)
|
|
setNoData(stream2)
|
|
|
|
fs := framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(fs[0].StreamID).To(Equal(stream1.StreamID()))
|
|
Expect(fs[0].Offset).To(Equal(offset))
|
|
Expect(fs[0].FinBit).To(BeTrue())
|
|
Expect(fs[0].Data).To(BeEmpty())
|
|
})
|
|
|
|
It("bundles FINs with data", func() {
|
|
offset := protocol.ByteCount(42)
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobar"), true)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset().Return(offset)
|
|
setNoData(stream2)
|
|
|
|
fs := framer.PopStreamFrames(1000)
|
|
Expect(fs).To(HaveLen(1))
|
|
Expect(fs[0].StreamID).To(Equal(stream1.StreamID()))
|
|
Expect(fs[0].Data).To(Equal([]byte("foobar")))
|
|
Expect(fs[0].FinBit).To(BeTrue())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("BLOCKED frames", func() {
|
|
It("Pop returns nil if no frame is queued", func() {
|
|
Expect(framer.PopBlockedFrame()).To(BeNil())
|
|
})
|
|
|
|
It("queues and pops BLOCKED frames for individually blocked streams", func() {
|
|
connFC.EXPECT().IsBlocked()
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foobar"), false)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
stream1.EXPECT().IsFlowControlBlocked().Return(true)
|
|
setNoData(stream2)
|
|
frames := framer.PopStreamFrames(1000)
|
|
Expect(frames).To(HaveLen(1))
|
|
f := framer.PopBlockedFrame()
|
|
Expect(f).To(BeAssignableToTypeOf(&wire.StreamBlockedFrame{}))
|
|
bf := f.(*wire.StreamBlockedFrame)
|
|
Expect(bf.StreamID).To(Equal(stream1.StreamID()))
|
|
Expect(framer.PopBlockedFrame()).To(BeNil())
|
|
})
|
|
|
|
It("does not queue a stream-level BLOCKED frame after sending the FinBit frame", func() {
|
|
connFC.EXPECT().IsBlocked()
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foo"), true)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
setNoData(stream2)
|
|
frames := framer.PopStreamFrames(1000)
|
|
Expect(frames).To(HaveLen(1))
|
|
Expect(frames[0].FinBit).To(BeTrue())
|
|
Expect(frames[0].DataLen()).To(Equal(protocol.ByteCount(3)))
|
|
blockedFrame := framer.PopBlockedFrame()
|
|
Expect(blockedFrame).To(BeNil())
|
|
})
|
|
|
|
It("queues and pops BLOCKED frames for connection blocked streams", func() {
|
|
connFC.EXPECT().IsBlocked().Return(true)
|
|
stream1.EXPECT().GetDataForWriting(gomock.Any()).Return([]byte("foo"), false)
|
|
stream1.EXPECT().HasDataForWriting().Return(true)
|
|
stream1.EXPECT().GetWriteOffset()
|
|
stream1.EXPECT().IsFlowControlBlocked().Return(false)
|
|
setNoData(stream2)
|
|
framer.PopStreamFrames(1000)
|
|
f := framer.PopBlockedFrame()
|
|
Expect(f).To(BeAssignableToTypeOf(&wire.BlockedFrame{}))
|
|
Expect(framer.PopBlockedFrame()).To(BeNil())
|
|
})
|
|
})
|
|
})
|