quic: add a data structure for tracking sent packets
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>
diff --git a/internal/quic/sent_packet.go b/internal/quic/sent_packet.go
new file mode 100644
index 0000000..03d5e53
--- /dev/null
+++ b/internal/quic/sent_packet.go
@@ -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)
+}
diff --git a/internal/quic/sent_packet_test.go b/internal/quic/sent_packet_test.go
new file mode 100644
index 0000000..08a3d8f
--- /dev/null
+++ b/internal/quic/sent_packet_test.go
@@ -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")
+ }
+}