442 lines
15 KiB
Go
442 lines
15 KiB
Go
|
package quic
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
|
||
|
"github.com/lucas-clemente/quic-go/frames"
|
||
|
"github.com/lucas-clemente/quic-go/protocol"
|
||
|
. "github.com/onsi/ginkgo"
|
||
|
. "github.com/onsi/gomega"
|
||
|
)
|
||
|
|
||
|
var _ = Describe("Stream Framer", func() {
|
||
|
var (
|
||
|
retransmittedFrame1, retransmittedFrame2 *frames.StreamFrame
|
||
|
framer *streamFramer
|
||
|
streamsMap *streamsMap
|
||
|
stream1, stream2 *stream
|
||
|
fcm *mockFlowControlHandler
|
||
|
)
|
||
|
|
||
|
BeforeEach(func() {
|
||
|
retransmittedFrame1 = &frames.StreamFrame{
|
||
|
StreamID: 5,
|
||
|
Data: []byte{0x13, 0x37},
|
||
|
}
|
||
|
retransmittedFrame2 = &frames.StreamFrame{
|
||
|
StreamID: 6,
|
||
|
Data: []byte{0xDE, 0xCA, 0xFB, 0xAD},
|
||
|
}
|
||
|
|
||
|
stream1 = &stream{streamID: 10}
|
||
|
stream2 = &stream{streamID: 11}
|
||
|
|
||
|
streamsMap = newStreamsMap(nil, protocol.PerspectiveServer, &mockConnectionParametersManager{})
|
||
|
streamsMap.putStream(stream1)
|
||
|
streamsMap.putStream(stream2)
|
||
|
|
||
|
fcm = newMockFlowControlHandler()
|
||
|
fcm.sendWindowSizes[stream1.streamID] = protocol.MaxByteCount
|
||
|
fcm.sendWindowSizes[stream2.streamID] = protocol.MaxByteCount
|
||
|
fcm.sendWindowSizes[retransmittedFrame1.StreamID] = protocol.MaxByteCount
|
||
|
fcm.sendWindowSizes[retransmittedFrame2.StreamID] = protocol.MaxByteCount
|
||
|
framer = newStreamFramer(streamsMap, fcm)
|
||
|
})
|
||
|
|
||
|
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() {
|
||
|
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() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fs := framer.PopStreamFrames(protocol.MaxByteCount)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].DataLenPresent).To(BeTrue())
|
||
|
})
|
||
|
|
||
|
Context("Popping", func() {
|
||
|
It("returns nil when popping an empty framer", func() {
|
||
|
Expect(framer.PopStreamFrames(1000)).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("pops frames for retransmission", func() {
|
||
|
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.dataForWriting = []byte("foobar")
|
||
|
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(framer.PopStreamFrames(1000)).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("returns multiple normal frames", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
stream2.dataForWriting = []byte("foobaz")
|
||
|
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")))
|
||
|
Expect(framer.PopStreamFrames(1000)).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("returns retransmission frames before normal frames", func() {
|
||
|
framer.AddFrameForRetransmission(retransmittedFrame1)
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(2))
|
||
|
Expect(fs[0]).To(Equal(retransmittedFrame1))
|
||
|
Expect(fs[1].StreamID).To(Equal(stream1.streamID))
|
||
|
Expect(framer.PopStreamFrames(1000)).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("does not pop empty frames", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fs := framer.PopStreamFrames(4)
|
||
|
Expect(fs).To(HaveLen(0))
|
||
|
fs = framer.PopStreamFrames(5)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).ToNot(BeEmpty())
|
||
|
Expect(fs[0].FinBit).To(BeFalse())
|
||
|
})
|
||
|
|
||
|
It("uses the round-robin scheduling", func() {
|
||
|
stream1.dataForWriting = bytes.Repeat([]byte("f"), 100)
|
||
|
stream2.dataForWriting = bytes.Repeat([]byte("e"), 100)
|
||
|
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 := &frames.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 := &frames.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() {
|
||
|
framer.AddFrameForRetransmission(retransmittedFrame2)
|
||
|
origlen := retransmittedFrame2.DataLen()
|
||
|
fs := framer.PopStreamFrames(6)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
minLength, _ := fs[0].MinLength(0)
|
||
|
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 := &frames.StreamFrame{
|
||
|
StreamID: 5,
|
||
|
Offset: 1,
|
||
|
FinBit: false,
|
||
|
Data: bytes.Repeat([]byte{'f'}, 30*30),
|
||
|
}
|
||
|
framer.AddFrameForRetransmission(origFrame)
|
||
|
|
||
|
minFrameDataLen := protocol.MaxFrameAndPublicHeaderSize
|
||
|
|
||
|
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() {
|
||
|
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())
|
||
|
})
|
||
|
|
||
|
It("gets the whole data of a frame if it was split", func() {
|
||
|
origdata := []byte("foobar")
|
||
|
stream1.dataForWriting = origdata
|
||
|
fs := framer.PopStreamFrames(7)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("foo")))
|
||
|
var b bytes.Buffer
|
||
|
fs[0].Write(&b, 0)
|
||
|
Expect(b.Len()).To(Equal(7))
|
||
|
fs = framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("bar")))
|
||
|
})
|
||
|
})
|
||
|
|
||
|
Context("sending FINs", func() {
|
||
|
It("sends FINs when streams are closed", func() {
|
||
|
stream1.writeOffset = 42
|
||
|
stream1.finishedWriting.Set(true)
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].StreamID).To(Equal(stream1.streamID))
|
||
|
Expect(fs[0].Offset).To(Equal(stream1.writeOffset))
|
||
|
Expect(fs[0].FinBit).To(BeTrue())
|
||
|
Expect(fs[0].Data).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("sends FINs when flow-control blocked", func() {
|
||
|
stream1.writeOffset = 42
|
||
|
stream1.finishedWriting.Set(true)
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 42
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].StreamID).To(Equal(stream1.streamID))
|
||
|
Expect(fs[0].Offset).To(Equal(stream1.writeOffset))
|
||
|
Expect(fs[0].FinBit).To(BeTrue())
|
||
|
Expect(fs[0].Data).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("bundles FINs with data", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
stream1.finishedWriting.Set(true)
|
||
|
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("flow control", func() {
|
||
|
It("tells the FlowControlManager how many bytes it sent", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
framer.PopStreamFrames(1000)
|
||
|
Expect(fcm.bytesSent).To(Equal(protocol.ByteCount(6)))
|
||
|
})
|
||
|
|
||
|
It("does not count retransmitted frames as sent bytes", func() {
|
||
|
framer.AddFrameForRetransmission(retransmittedFrame1)
|
||
|
framer.PopStreamFrames(1000)
|
||
|
Expect(fcm.bytesSent).To(BeZero())
|
||
|
})
|
||
|
|
||
|
It("returns the whole frame if it fits", func() {
|
||
|
stream1.writeOffset = 10
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fcm.sendWindowSizes[stream1.streamID] = 10 + 6
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].DataLen()).To(Equal(protocol.ByteCount(6)))
|
||
|
})
|
||
|
|
||
|
It("returns a smaller frame if the whole frame doesn't fit", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fcm.sendWindowSizes[stream1.streamID] = 3
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("foo")))
|
||
|
})
|
||
|
|
||
|
It("returns a smaller frame if the whole frame doesn't fit in the stream flow control window, for non-zero StreamFrame offset", func() {
|
||
|
stream1.writeOffset = 1
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 3
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("foo")))
|
||
|
})
|
||
|
|
||
|
It("returns a smaller frame if the whole frame doesn't fit in the connection flow control window", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fcm.streamsContributing = []protocol.StreamID{stream1.StreamID()}
|
||
|
fcm.remainingConnectionWindowSize = 3
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("foo")))
|
||
|
})
|
||
|
|
||
|
It("ignores the connection flow control window for non-contributing streams", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fcm.remainingConnectionWindowSize = 0
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("foobar")))
|
||
|
})
|
||
|
|
||
|
It("respects the connection flow control window for contributing streams", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
fcm.remainingConnectionWindowSize = 0
|
||
|
fcm.streamsContributing = []protocol.StreamID{stream1.StreamID()}
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("selects a stream that is not flow control blocked", func() {
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 0
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
stream2.dataForWriting = []byte("foobaz")
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].StreamID).To(Equal(stream2.StreamID()))
|
||
|
Expect(fs[0].Data).To(Equal([]byte("foobaz")))
|
||
|
})
|
||
|
|
||
|
It("chooses a non-contributing stream if the connection is flow control blocked", func() {
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
stream2.dataForWriting = []byte("foobaz")
|
||
|
fcm.streamsContributing = []protocol.StreamID{stream1.StreamID()}
|
||
|
fcm.remainingConnectionWindowSize = 0
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(HaveLen(1))
|
||
|
Expect(fs[0].StreamID).To(Equal(stream2.StreamID()))
|
||
|
})
|
||
|
|
||
|
It("returns nil if every stream is individually flow control blocked", func() {
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 0
|
||
|
fcm.sendWindowSizes[stream2.StreamID()] = 0
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
stream2.dataForWriting = []byte("foobaz")
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(BeEmpty())
|
||
|
})
|
||
|
|
||
|
It("returns nil if every stream is connection flow control blocked", func() {
|
||
|
fcm.remainingConnectionWindowSize = 0
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
stream2.dataForWriting = []byte("foobaz")
|
||
|
fcm.streamsContributing = []protocol.StreamID{stream1.StreamID(), stream2.StreamID()}
|
||
|
fs := framer.PopStreamFrames(1000)
|
||
|
Expect(fs).To(BeEmpty())
|
||
|
})
|
||
|
})
|
||
|
|
||
|
Context("BLOCKED frames", func() {
|
||
|
BeforeEach(func() {
|
||
|
fcm.remainingConnectionWindowSize = protocol.MaxByteCount
|
||
|
})
|
||
|
|
||
|
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() {
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 3
|
||
|
stream1.dataForWriting = []byte("foo")
|
||
|
frames := framer.PopStreamFrames(1000)
|
||
|
Expect(frames).To(HaveLen(1))
|
||
|
blockedFrame := framer.PopBlockedFrame()
|
||
|
Expect(blockedFrame).ToNot(BeNil())
|
||
|
Expect(blockedFrame.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() {
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 5000
|
||
|
stream1.dataForWriting = []byte("foo")
|
||
|
frames := framer.PopStreamFrames(1000)
|
||
|
Expect(frames).To(HaveLen(1))
|
||
|
Expect(frames[0].FinBit).To(BeFalse())
|
||
|
stream1.finishedWriting.Set(true)
|
||
|
frames = framer.PopStreamFrames(1000)
|
||
|
Expect(frames).To(HaveLen(1))
|
||
|
Expect(frames[0].FinBit).To(BeTrue())
|
||
|
Expect(frames[0].DataLen()).To(BeZero())
|
||
|
blockedFrame := framer.PopBlockedFrame()
|
||
|
Expect(blockedFrame).To(BeNil())
|
||
|
})
|
||
|
|
||
|
It("queues and pops BLOCKED frames for connection blocked streams", func() {
|
||
|
fcm.remainingConnectionWindowSize = 3
|
||
|
fcm.streamsContributing = []protocol.StreamID{stream1.StreamID()}
|
||
|
stream1.dataForWriting = []byte("foo")
|
||
|
framer.PopStreamFrames(1000)
|
||
|
blockedFrame := framer.PopBlockedFrame()
|
||
|
Expect(blockedFrame).ToNot(BeNil())
|
||
|
Expect(blockedFrame.StreamID).To(BeZero())
|
||
|
Expect(framer.PopBlockedFrame()).To(BeNil())
|
||
|
})
|
||
|
|
||
|
It("does not queue BLOCKED frames for non-contributing streams", func() {
|
||
|
fcm.remainingConnectionWindowSize = 3
|
||
|
stream1.dataForWriting = []byte("foo")
|
||
|
framer.PopStreamFrames(1000)
|
||
|
Expect(framer.PopBlockedFrame()).To(BeNil())
|
||
|
})
|
||
|
|
||
|
It("does not queue BLOCKED frames twice", func() {
|
||
|
fcm.sendWindowSizes[stream1.StreamID()] = 3
|
||
|
stream1.dataForWriting = []byte("foobar")
|
||
|
framer.PopStreamFrames(1000)
|
||
|
blockedFrame := framer.PopBlockedFrame()
|
||
|
Expect(blockedFrame).ToNot(BeNil())
|
||
|
Expect(blockedFrame.StreamID).To(Equal(stream1.StreamID()))
|
||
|
Expect(framer.PopBlockedFrame()).To(BeNil())
|
||
|
})
|
||
|
})
|
||
|
})
|