-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement the datagram draft #2162
Merged
Merged
Changes from 4 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
eefd2b7
implement parsing and writing of the DATAGRAM frames
marten-seemann 0582228
make it possible to log DATAGRAM frames
marten-seemann c4de242
add a MaxDataLen function for DATAGRAM frames
marten-seemann d6eff22
introduce a sentinel ByteCount value
marten-seemann 021f70a
implement the max_datagram_frame_size transport parameter
marten-seemann 356c699
return a quic.ConnectionState from Session.ConnectionState()
marten-seemann e951646
tell if a peer supports DATAGRAM frames in the ConnectionState
marten-seemann c47ccab
implement a datagram queue
marten-seemann 231bc91
implement packing of DATAGRAM frames
marten-seemann 913ddc5
add a quic.Config option for DATAGRAM frames
marten-seemann fd41e8f
reject DATAGRAM frames if datagram support is not enabled
marten-seemann fdaac4f
send the max_datagram_frame_size transport parameter
marten-seemann 791f896
implement sending of datagrams
marten-seemann 9814536
implement receiving of DATAGRAM frames
marten-seemann 1728a6c
add an integration test for datagram transfers
marten-seemann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package wire | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
|
||
"github.com/lucas-clemente/quic-go/internal/protocol" | ||
"github.com/lucas-clemente/quic-go/internal/utils" | ||
) | ||
|
||
// A DatagramFrame is a DATAGRAM frame | ||
type DatagramFrame struct { | ||
DataLenPresent bool | ||
Data []byte | ||
} | ||
|
||
func parseDatagramFrame(r *bytes.Reader, _ protocol.VersionNumber) (*DatagramFrame, error) { | ||
typeByte, err := r.ReadByte() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
f := &DatagramFrame{} | ||
f.DataLenPresent = typeByte&0x1 > 0 | ||
|
||
var length uint64 | ||
if f.DataLenPresent { | ||
var err error | ||
len, err := utils.ReadVarInt(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len > uint64(r.Len()) { | ||
return nil, io.EOF | ||
} | ||
length = len | ||
} else { | ||
length = uint64(r.Len()) | ||
} | ||
f.Data = make([]byte, length) | ||
if _, err := io.ReadFull(r, f.Data); err != nil { | ||
return nil, err | ||
} | ||
return f, nil | ||
} | ||
|
||
func (f *DatagramFrame) Write(b *bytes.Buffer, _ protocol.VersionNumber) error { | ||
typeByte := uint8(0x30) | ||
if f.DataLenPresent { | ||
typeByte ^= 0x1 | ||
} | ||
b.WriteByte(typeByte) | ||
if f.DataLenPresent { | ||
utils.WriteVarInt(b, uint64(len(f.Data))) | ||
} | ||
b.Write(f.Data) | ||
return nil | ||
} | ||
|
||
// MaxDataLen returns the maximum data length | ||
func (f *DatagramFrame) MaxDataLen(maxSize protocol.ByteCount, version protocol.VersionNumber) protocol.ByteCount { | ||
headerLen := protocol.ByteCount(1) | ||
if f.DataLenPresent { | ||
// 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++ | ||
} | ||
if headerLen > maxSize { | ||
return 0 | ||
} | ||
maxDataLen := maxSize - headerLen | ||
if f.DataLenPresent && utils.VarIntLen(uint64(maxDataLen)) != 1 { | ||
maxDataLen-- | ||
} | ||
return maxDataLen | ||
} | ||
|
||
// Length of a written frame | ||
func (f *DatagramFrame) Length(_ protocol.VersionNumber) protocol.ByteCount { | ||
length := 1 + protocol.ByteCount(len(f.Data)) | ||
if f.DataLenPresent { | ||
length += utils.VarIntLen(uint64(len(f.Data))) | ||
} | ||
return length | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package wire | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
|
||
"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("STREAM frame", func() { | ||
Context("when parsing", func() { | ||
It("parses a frame containing a length", func() { | ||
data := []byte{0x30 ^ 0x1} | ||
data = append(data, encodeVarInt(0x6)...) // length | ||
data = append(data, []byte("foobar")...) | ||
r := bytes.NewReader(data) | ||
frame, err := parseDatagramFrame(r, versionIETFFrames) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(frame.Data).To(Equal([]byte("foobar"))) | ||
Expect(frame.DataLenPresent).To(BeTrue()) | ||
Expect(r.Len()).To(BeZero()) | ||
}) | ||
|
||
It("parses a frame without length", func() { | ||
data := []byte{0x30} | ||
data = append(data, []byte("Lorem ipsum dolor sit amet")...) | ||
r := bytes.NewReader(data) | ||
frame, err := parseDatagramFrame(r, versionIETFFrames) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(frame.Data).To(Equal([]byte("Lorem ipsum dolor sit amet"))) | ||
Expect(frame.DataLenPresent).To(BeFalse()) | ||
Expect(r.Len()).To(BeZero()) | ||
}) | ||
|
||
It("errors when the length is longer than the rest of the frame", func() { | ||
data := []byte{0x30 ^ 0x1} | ||
data = append(data, encodeVarInt(0x6)...) // length | ||
data = append(data, []byte("fooba")...) | ||
r := bytes.NewReader(data) | ||
_, err := parseDatagramFrame(r, versionIETFFrames) | ||
Expect(err).To(MatchError(io.EOF)) | ||
}) | ||
|
||
It("errors on EOFs", func() { | ||
data := []byte{0x30 ^ 0x1} | ||
data = append(data, encodeVarInt(6)...) // length | ||
data = append(data, []byte("foobar")...) | ||
_, err := parseDatagramFrame(bytes.NewReader(data), versionIETFFrames) | ||
Expect(err).NotTo(HaveOccurred()) | ||
for i := range data { | ||
_, err := parseDatagramFrame(bytes.NewReader(data[0:i]), versionIETFFrames) | ||
Expect(err).To(MatchError(io.EOF)) | ||
} | ||
}) | ||
}) | ||
|
||
Context("when writing", func() { | ||
It("writes a frame with length", func() { | ||
f := &DatagramFrame{ | ||
DataLenPresent: true, | ||
Data: []byte("foobar"), | ||
} | ||
buf := &bytes.Buffer{} | ||
Expect(f.Write(buf, versionIETFFrames)).To(Succeed()) | ||
expected := []byte{0x30 ^ 0x1} | ||
expected = append(expected, encodeVarInt(0x6)...) | ||
expected = append(expected, []byte("foobar")...) | ||
Expect(buf.Bytes()).To(Equal(expected)) | ||
}) | ||
|
||
It("writes a frame without length", func() { | ||
f := &DatagramFrame{Data: []byte("Lorem ipsum")} | ||
buf := &bytes.Buffer{} | ||
Expect(f.Write(buf, versionIETFFrames)).To(Succeed()) | ||
expected := []byte{0x30} | ||
expected = append(expected, []byte("Lorem ipsum")...) | ||
Expect(buf.Bytes()).To(Equal(expected)) | ||
}) | ||
}) | ||
|
||
Context("length", func() { | ||
It("has the right length for a frame with length", func() { | ||
f := &DatagramFrame{ | ||
DataLenPresent: true, | ||
Data: []byte("foobar"), | ||
} | ||
Expect(f.Length(versionIETFFrames)).To(Equal(1 + utils.VarIntLen(6) + 6)) | ||
}) | ||
|
||
It("has the right length for a frame without length", func() { | ||
f := &DatagramFrame{Data: []byte("foobar")} | ||
Expect(f.Length(versionIETFFrames)).To(Equal(protocol.ByteCount(1 + 6))) | ||
}) | ||
}) | ||
|
||
Context("max data length", func() { | ||
const maxSize = 3000 | ||
|
||
It("returns a data length such that the resulting frame has the right size, if data length is not present", func() { | ||
data := make([]byte, maxSize) | ||
f := &DatagramFrame{} | ||
b := &bytes.Buffer{} | ||
for i := 1; i < 3000; i++ { | ||
b.Reset() | ||
f.Data = nil | ||
maxDataLen := f.MaxDataLen(protocol.ByteCount(i), versionIETFFrames) | ||
if maxDataLen == 0 { // 0 means that no valid STREAM frame can be written | ||
// check that writing a minimal size STREAM frame (i.e. with 1 byte data) is actually larger than the desired size | ||
f.Data = []byte{0} | ||
Expect(f.Write(b, versionIETFFrames)).To(Succeed()) | ||
Expect(b.Len()).To(BeNumerically(">", i)) | ||
continue | ||
} | ||
f.Data = data[:int(maxDataLen)] | ||
Expect(f.Write(b, versionIETFFrames)).To(Succeed()) | ||
Expect(b.Len()).To(Equal(i)) | ||
} | ||
}) | ||
|
||
It("always returns a data length such that the resulting frame has the right size, if data length is present", func() { | ||
data := make([]byte, maxSize) | ||
f := &DatagramFrame{DataLenPresent: true} | ||
b := &bytes.Buffer{} | ||
var frameOneByteTooSmallCounter int | ||
for i := 1; i < 3000; i++ { | ||
b.Reset() | ||
f.Data = nil | ||
maxDataLen := f.MaxDataLen(protocol.ByteCount(i), versionIETFFrames) | ||
if maxDataLen == 0 { // 0 means that no valid STREAM frame can be written | ||
// check that writing a minimal size STREAM frame (i.e. with 1 byte data) is actually larger than the desired size | ||
f.Data = []byte{0} | ||
Expect(f.Write(b, versionIETFFrames)).To(Succeed()) | ||
Expect(b.Len()).To(BeNumerically(">", i)) | ||
continue | ||
} | ||
f.Data = data[:int(maxDataLen)] | ||
Expect(f.Write(b, versionIETFFrames)).To(Succeed()) | ||
// 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)) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems scary to me :S Are we sure this doesn't break any assumptions around non-negative numbers sprinkled throughout the codebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It did uncover a uint underflow, fortunately it was just in test code (in the flow controller). I'm not sure why this is scary, as QUIC's varint encoding makes sure that all numbers that can practically be used are limited to
MaxUint64 / 4
.