mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 04:37:36 +03:00
By introducing a callback to the stream, which the stream calls as soon as it is completed, we can get rid of checking every single open stream if it is completed.
648 lines
22 KiB
Go
648 lines
22 KiB
Go
package quic
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"runtime"
|
|
"time"
|
|
|
|
"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"
|
|
"github.com/onsi/gomega/gbytes"
|
|
)
|
|
|
|
var _ = Describe("Receive Stream", func() {
|
|
const streamID protocol.StreamID = 1337
|
|
|
|
var (
|
|
str *receiveStream
|
|
strWithTimeout io.Reader // str wrapped with gbytes.TimeoutReader
|
|
mockFC *mocks.MockStreamFlowController
|
|
mockSender *MockStreamSender
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
mockSender = NewMockStreamSender(mockCtrl)
|
|
mockFC = mocks.NewMockStreamFlowController(mockCtrl)
|
|
str = newReceiveStream(streamID, mockSender, mockFC)
|
|
|
|
timeout := scaleDuration(250 * time.Millisecond)
|
|
strWithTimeout = gbytes.TimeoutReader(str, timeout)
|
|
})
|
|
|
|
It("gets stream id", func() {
|
|
Expect(str.StreamID()).To(Equal(protocol.StreamID(1337)))
|
|
})
|
|
|
|
Context("reading", func() {
|
|
It("reads a single STREAM frame", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(4))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
frame := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD, 0xBE, 0xEF},
|
|
}
|
|
err := str.handleStreamFrame(&frame)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
|
|
})
|
|
|
|
It("reads a single STREAM frame in multiple goes", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2))
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2))
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD, 0xBE, 0xEF},
|
|
}
|
|
err := str.handleStreamFrame(&frame)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 2)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(2))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD}))
|
|
n, err = strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(2))
|
|
Expect(b).To(Equal([]byte{0xBE, 0xEF}))
|
|
})
|
|
|
|
It("reads all data available", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2)).Times(2)
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD},
|
|
}
|
|
frame2 := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte{0xBE, 0xEF},
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame2)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 6)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00}))
|
|
})
|
|
|
|
It("assembles multiple STREAM frames", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2)).Times(2)
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD},
|
|
}
|
|
frame2 := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte{0xBE, 0xEF},
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame2)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
|
|
})
|
|
|
|
It("waits until data is available", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
frame := wire.StreamFrame{Data: []byte{0xDE, 0xAD}}
|
|
time.Sleep(10 * time.Millisecond)
|
|
err := str.handleStreamFrame(&frame)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}()
|
|
b := make([]byte, 2)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(2))
|
|
})
|
|
|
|
It("handles STREAM frames in wrong order", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2)).Times(2)
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte{0xBE, 0xEF},
|
|
}
|
|
frame2 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD},
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame2)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
|
|
})
|
|
|
|
It("ignores duplicate STREAM frames", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2)).Times(2)
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD},
|
|
}
|
|
frame2 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0x13, 0x37},
|
|
}
|
|
frame3 := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte{0xBE, 0xEF},
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame2)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame3)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
|
|
})
|
|
|
|
It("doesn't rejects a STREAM frames with an overlapping data range", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(6), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2))
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(4))
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte("foob"),
|
|
}
|
|
frame2 := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte("obar"),
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame2)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 6)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(6))
|
|
Expect(b).To(Equal([]byte("foobar")))
|
|
})
|
|
|
|
It("passes on errors from the streamFrameSorter", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(0), false)
|
|
err := str.handleStreamFrame(&wire.StreamFrame{StreamID: streamID}) // STREAM frame without data
|
|
Expect(err).To(MatchError(errEmptyStreamData))
|
|
})
|
|
|
|
It("calls the onHasWindowUpdate callback, when the a MAX_STREAM_DATA should be sent", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(6), false)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(6))
|
|
mockFC.EXPECT().HasWindowUpdate().Return(true)
|
|
mockSender.EXPECT().onHasWindowUpdate(streamID)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte("foobar"),
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b := make([]byte, 6)
|
|
_, err = strWithTimeout.Read(b)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
Context("deadlines", func() {
|
|
It("the deadline error has the right net.Error properties", func() {
|
|
Expect(errDeadline.Temporary()).To(BeTrue())
|
|
Expect(errDeadline.Timeout()).To(BeTrue())
|
|
Expect(errDeadline).To(MatchError("deadline exceeded"))
|
|
})
|
|
|
|
It("returns an error when Read is called after the deadline", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(6), false).AnyTimes()
|
|
f := &wire.StreamFrame{Data: []byte("foobar")}
|
|
err := str.handleStreamFrame(f)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
str.SetReadDeadline(time.Now().Add(-time.Second))
|
|
b := make([]byte, 6)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(n).To(BeZero())
|
|
})
|
|
|
|
It("unblocks after the deadline", func() {
|
|
deadline := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
str.SetReadDeadline(deadline)
|
|
b := make([]byte, 6)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(n).To(BeZero())
|
|
Expect(time.Now()).To(BeTemporally("~", deadline, scaleDuration(10*time.Millisecond)))
|
|
})
|
|
|
|
It("doesn't unblock if the deadline is changed before the first one expires", func() {
|
|
deadline1 := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
deadline2 := time.Now().Add(scaleDuration(100 * time.Millisecond))
|
|
str.SetReadDeadline(deadline1)
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
time.Sleep(scaleDuration(20 * time.Millisecond))
|
|
str.SetReadDeadline(deadline2)
|
|
// make sure that this was actually execute before the deadline expires
|
|
Expect(time.Now()).To(BeTemporally("<", deadline1))
|
|
}()
|
|
runtime.Gosched()
|
|
b := make([]byte, 10)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(n).To(BeZero())
|
|
Expect(time.Now()).To(BeTemporally("~", deadline2, scaleDuration(20*time.Millisecond)))
|
|
})
|
|
|
|
It("unblocks earlier, when a new deadline is set", func() {
|
|
deadline1 := time.Now().Add(scaleDuration(200 * time.Millisecond))
|
|
deadline2 := time.Now().Add(scaleDuration(50 * time.Millisecond))
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
time.Sleep(scaleDuration(10 * time.Millisecond))
|
|
str.SetReadDeadline(deadline2)
|
|
// make sure that this was actually execute before the deadline expires
|
|
Expect(time.Now()).To(BeTemporally("<", deadline2))
|
|
}()
|
|
str.SetReadDeadline(deadline1)
|
|
runtime.Gosched()
|
|
b := make([]byte, 10)
|
|
_, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(errDeadline))
|
|
Expect(time.Now()).To(BeTemporally("~", deadline2, scaleDuration(25*time.Millisecond)))
|
|
})
|
|
})
|
|
|
|
Context("closing", func() {
|
|
Context("with FIN bit", func() {
|
|
It("returns EOFs", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), true)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(4))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
str.handleStreamFrame(&wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD, 0xBE, 0xEF},
|
|
FinBit: true,
|
|
})
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(io.EOF))
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
|
|
n, err = strWithTimeout.Read(b)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(io.EOF))
|
|
})
|
|
|
|
It("handles out-of-order frames", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), false)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(4), true)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2)).Times(2)
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
frame1 := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte{0xBE, 0xEF},
|
|
FinBit: true,
|
|
}
|
|
frame2 := wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xDE, 0xAD},
|
|
}
|
|
err := str.handleStreamFrame(&frame1)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleStreamFrame(&frame2)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(io.EOF))
|
|
Expect(n).To(Equal(4))
|
|
Expect(b).To(Equal([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
|
|
n, err = strWithTimeout.Read(b)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(io.EOF))
|
|
})
|
|
|
|
It("returns EOFs with partial read", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(2), true)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
err := str.handleStreamFrame(&wire.StreamFrame{
|
|
Offset: 0,
|
|
Data: []byte{0xde, 0xad},
|
|
FinBit: true,
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(err).To(MatchError(io.EOF))
|
|
Expect(n).To(Equal(2))
|
|
Expect(b[:n]).To(Equal([]byte{0xde, 0xad}))
|
|
})
|
|
|
|
It("handles immediate FINs", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(0), true)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(0))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
err := str.handleStreamFrame(&wire.StreamFrame{
|
|
Offset: 0,
|
|
FinBit: true,
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
b := make([]byte, 4)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(io.EOF))
|
|
})
|
|
})
|
|
|
|
It("closes when CloseRemote is called", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(0), true)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(0))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
str.CloseRemote(0)
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
b := make([]byte, 8)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(io.EOF))
|
|
})
|
|
})
|
|
|
|
Context("closing for shutdown", func() {
|
|
testErr := errors.New("test error")
|
|
|
|
It("immediately returns all reads", func() {
|
|
done := make(chan struct{})
|
|
b := make([]byte, 4)
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(testErr))
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
str.closeForShutdown(testErr)
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("errors for all following reads", func() {
|
|
str.closeForShutdown(testErr)
|
|
b := make([]byte, 1)
|
|
n, err := strWithTimeout.Read(b)
|
|
Expect(n).To(BeZero())
|
|
Expect(err).To(MatchError(testErr))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("stream cancelations", func() {
|
|
Context("canceling read", func() {
|
|
It("unblocks Read", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := strWithTimeout.Read([]byte{0})
|
|
Expect(err).To(MatchError("Read on stream 1337 canceled with error code 1234"))
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
err := str.CancelRead(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("doesn't allow further calls to Read", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
err := str.CancelRead(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = strWithTimeout.Read([]byte{0})
|
|
Expect(err).To(MatchError("Read on stream 1337 canceled with error code 1234"))
|
|
})
|
|
|
|
It("does nothing when CancelRead is called twice", func() {
|
|
mockSender.EXPECT().queueControlFrame(gomock.Any())
|
|
err := str.CancelRead(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.CancelRead(2345)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = strWithTimeout.Read([]byte{0})
|
|
Expect(err).To(MatchError("Read on stream 1337 canceled with error code 1234"))
|
|
})
|
|
|
|
It("doesn't send a RST_STREAM frame, if the FIN was already read", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(6), true)
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(6))
|
|
mockFC.EXPECT().HasWindowUpdate()
|
|
// no calls to mockSender.queueControlFrame
|
|
err := str.handleStreamFrame(&wire.StreamFrame{
|
|
StreamID: streamID,
|
|
Data: []byte("foobar"),
|
|
FinBit: true,
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
_, err = strWithTimeout.Read(make([]byte, 100))
|
|
Expect(err).To(MatchError(io.EOF))
|
|
err = str.CancelRead(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("queues a STOP_SENDING frame, for IETF QUIC", func() {
|
|
mockSender.EXPECT().queueControlFrame(&wire.StopSendingFrame{
|
|
StreamID: streamID,
|
|
ErrorCode: 1234,
|
|
})
|
|
err := str.CancelRead(1234)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("doesn't queue a STOP_SENDING frame, for gQUIC", func() {
|
|
|
|
})
|
|
})
|
|
|
|
Context("receiving RST_STREAM frames", func() {
|
|
rst := &wire.RstStreamFrame{
|
|
StreamID: streamID,
|
|
ByteOffset: 42,
|
|
ErrorCode: 1234,
|
|
}
|
|
|
|
It("unblocks Read", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(42), true)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := strWithTimeout.Read([]byte{0})
|
|
Expect(err).To(MatchError("Stream 1337 was reset with error code 1234"))
|
|
Expect(err).To(BeAssignableToTypeOf(streamCanceledError{}))
|
|
Expect(err.(streamCanceledError).Canceled()).To(BeTrue())
|
|
Expect(err.(streamCanceledError).ErrorCode()).To(Equal(protocol.ApplicationErrorCode(1234)))
|
|
close(done)
|
|
}()
|
|
Consistently(done).ShouldNot(BeClosed())
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
str.handleRstStreamFrame(rst)
|
|
Eventually(done).Should(BeClosed())
|
|
})
|
|
|
|
It("doesn't allow further calls to Read", func() {
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(42), true)
|
|
err := str.handleRstStreamFrame(rst)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = strWithTimeout.Read([]byte{0})
|
|
Expect(err).To(MatchError("Stream 1337 was reset with error code 1234"))
|
|
Expect(err).To(BeAssignableToTypeOf(streamCanceledError{}))
|
|
Expect(err.(streamCanceledError).Canceled()).To(BeTrue())
|
|
Expect(err.(streamCanceledError).ErrorCode()).To(Equal(protocol.ApplicationErrorCode(1234)))
|
|
})
|
|
|
|
It("errors when receiving a RST_STREAM with an inconsistent offset", func() {
|
|
testErr := errors.New("already received a different final offset before")
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(42), true).Return(testErr)
|
|
err := str.handleRstStreamFrame(rst)
|
|
Expect(err).To(MatchError(testErr))
|
|
})
|
|
|
|
It("ignores duplicate RST_STREAM frames", func() {
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(42), true).Times(2)
|
|
err := str.handleRstStreamFrame(rst)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleRstStreamFrame(rst)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("doesn't do anyting when it was closed for shutdown", func() {
|
|
str.closeForShutdown(nil)
|
|
err := str.handleRstStreamFrame(rst)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
Context("for gQUIC", func() {
|
|
BeforeEach(func() {
|
|
str.version = versionGQUICFrames
|
|
})
|
|
|
|
It("unblocks Read when receiving a RST_STREAM frame with non-zero error code", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(42), true)
|
|
readReturned := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
_, err := strWithTimeout.Read([]byte{0})
|
|
Expect(err).To(MatchError("Stream 1337 was reset with error code 1234"))
|
|
Expect(err).To(BeAssignableToTypeOf(streamCanceledError{}))
|
|
Expect(err.(streamCanceledError).Canceled()).To(BeTrue())
|
|
Expect(err.(streamCanceledError).ErrorCode()).To(Equal(protocol.ApplicationErrorCode(1234)))
|
|
close(readReturned)
|
|
}()
|
|
Consistently(readReturned).ShouldNot(BeClosed())
|
|
mockSender.EXPECT().onStreamCompleted(streamID)
|
|
err := str.handleRstStreamFrame(rst)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Eventually(readReturned).Should(BeClosed())
|
|
})
|
|
|
|
It("continues reading until the end when receiving a RST_STREAM frame with error code 0", func() {
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(6), true).Times(2)
|
|
gomock.InOrder(
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(4)),
|
|
mockFC.EXPECT().AddBytesRead(protocol.ByteCount(2)),
|
|
mockSender.EXPECT().onStreamCompleted(streamID),
|
|
)
|
|
mockFC.EXPECT().HasWindowUpdate().Times(2)
|
|
readReturned := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
n, err := strWithTimeout.Read(make([]byte, 4))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(n).To(Equal(4))
|
|
n, err = strWithTimeout.Read(make([]byte, 4))
|
|
Expect(err).To(MatchError(io.EOF))
|
|
Expect(n).To(Equal(2))
|
|
close(readReturned)
|
|
}()
|
|
Consistently(readReturned).ShouldNot(BeClosed())
|
|
err := str.handleStreamFrame(&wire.StreamFrame{
|
|
StreamID: streamID,
|
|
Data: []byte("foobar"),
|
|
FinBit: true,
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = str.handleRstStreamFrame(&wire.RstStreamFrame{
|
|
StreamID: streamID,
|
|
ByteOffset: 6,
|
|
ErrorCode: 0,
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Eventually(readReturned).Should(BeClosed())
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("flow control", func() {
|
|
It("errors when a STREAM frame causes a flow control violation", func() {
|
|
testErr := errors.New("flow control violation")
|
|
mockFC.EXPECT().UpdateHighestReceived(protocol.ByteCount(8), false).Return(testErr)
|
|
frame := wire.StreamFrame{
|
|
Offset: 2,
|
|
Data: []byte("foobar"),
|
|
}
|
|
err := str.handleStreamFrame(&frame)
|
|
Expect(err).To(MatchError(testErr))
|
|
})
|
|
|
|
It("gets a window update", func() {
|
|
mockFC.EXPECT().GetWindowUpdate().Return(protocol.ByteCount(0x100))
|
|
Expect(str.getWindowUpdate()).To(Equal(protocol.ByteCount(0x100)))
|
|
})
|
|
})
|
|
})
|