Skip to content

Commit

Permalink
quic: add a data structure for tracking sent packets
Browse files Browse the repository at this point in the history
When we send a packet, we need to remember its contents until it has
been acked or detected as lost.

For golang/go#58547

Change-Id: I8c18f7ca1730a3ce460cd562d060dd6c7cfa9ffb
Reviewed-on: https://go-review.googlesource.com/c/net/+/495236
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
neild committed May 25, 2023
1 parent 61d852e commit a233290
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 0 deletions.
100 changes: 100 additions & 0 deletions internal/quic/sent_packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package quic

import (
"sync"
"time"
)

// A sentPacket tracks state related to an in-flight packet we sent,
// to be committed when the peer acks it or resent if the packet is lost.
type sentPacket struct {
num packetNumber
size int // size in bytes
time time.Time // time sent

ackEliciting bool // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.4.1
inFlight bool // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.6.1
acked bool // ack has been received
lost bool // packet is presumed lost

// Frames sent in the packet.
//
// This is an abbreviated version of the packet payload, containing only the information
// we need to process an ack for or loss of this packet.
// For example, a CRYPTO frame is recorded as the frame type (0x06), offset, and length,
// but does not include the sent data.
b []byte
n int // read offset into b
}

var sentPool = sync.Pool{
New: func() interface{} {
return &sentPacket{}
},
}

func newSentPacket() *sentPacket {
sent := sentPool.Get().(*sentPacket)
sent.reset()
return sent
}

// recycle returns a sentPacket to the pool.
func (sent *sentPacket) recycle() {
sentPool.Put(sent)
}

func (sent *sentPacket) reset() {
*sent = sentPacket{
b: sent.b[:0],
}
}

// The append* methods record information about frames in the packet.

func (sent *sentPacket) appendNonAckElicitingFrame(frameType byte) {
sent.b = append(sent.b, frameType)
}

func (sent *sentPacket) appendAckElicitingFrame(frameType byte) {
sent.ackEliciting = true
sent.inFlight = true
sent.b = append(sent.b, frameType)
}

func (sent *sentPacket) appendInt(v uint64) {
sent.b = appendVarint(sent.b, v)
}

func (sent *sentPacket) appendOffAndSize(start int64, size int) {
sent.b = appendVarint(sent.b, uint64(start))
sent.b = appendVarint(sent.b, uint64(size))
}

// The next* methods read back information about frames in the packet.

func (sent *sentPacket) next() (frameType byte) {
f := sent.b[sent.n]
sent.n++
return f
}

func (sent *sentPacket) nextInt() uint64 {
v, n := consumeVarint(sent.b[sent.n:])
sent.n += n
return v
}

func (sent *sentPacket) nextRange() (start, end int64) {
start = int64(sent.nextInt())
end = start + int64(sent.nextInt())
return start, end
}

func (sent *sentPacket) done() bool {
return sent.n == len(sent.b)
}
51 changes: 51 additions & 0 deletions internal/quic/sent_packet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package quic

import "testing"

func TestSentPacket(t *testing.T) {
frames := []interface{}{
byte(frameTypePing),
byte(frameTypeStreamBase),
uint64(1),
i64range{1 << 20, 1<<20 + 1024},
}
// Record sent frames.
sent := newSentPacket()
for _, f := range frames {
switch f := f.(type) {
case byte:
sent.appendAckElicitingFrame(f)
case uint64:
sent.appendInt(f)
case i64range:
sent.appendOffAndSize(f.start, int(f.size()))
}
}
// Read the record.
for i, want := range frames {
if done := sent.done(); done {
t.Fatalf("before consuming contents, sent.done() = true, want false")
}
switch want := want.(type) {
case byte:
if got := sent.next(); got != want {
t.Fatalf("%v: sent.next() = %v, want %v", i, got, want)
}
case uint64:
if got := sent.nextInt(); got != want {
t.Fatalf("%v: sent.nextInt() = %v, want %v", i, got, want)
}
case i64range:
if start, end := sent.nextRange(); start != want.start || end != want.end {
t.Fatalf("%v: sent.nextRange() = [%v,%v), want %v", i, start, end, want)
}
}
}
if done := sent.done(); !done {
t.Fatalf("after consuming contents, sent.done() = false, want true")
}
}

0 comments on commit a233290

Please sign in to comment.