From a233290d3062ca7801ab3b804a4d7ee5d0e14253 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 13 Oct 2022 21:38:10 -0700 Subject: [PATCH] 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 Reviewed-by: Cuong Manh Le Run-TryBot: Damien Neil TryBot-Result: Gopher Robot --- internal/quic/sent_packet.go | 100 ++++++++++++++++++++++++++++++ internal/quic/sent_packet_test.go | 51 +++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 internal/quic/sent_packet.go create mode 100644 internal/quic/sent_packet_test.go diff --git a/internal/quic/sent_packet.go b/internal/quic/sent_packet.go new file mode 100644 index 0000000000..03d5e53d17 --- /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 0000000000..08a3d8ff03 --- /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") + } +}