quic: add a type tracking sent values
Any given datum communicated to the peer follows a state machine:
- We do not need to send the this datum.
- We need to send it, but have not done so.
- We have sent it, but the peer has not acknowledged it.
- We have sent it and the peer has acknowledged it.
Data transitions between states in a consistent fashion; for example,
loss of the most recent packet containing a HANDSHAKE_DONE frame
means we should resend the frame in a new packet.
Add a sentVal type which tracks this state machine.
For golang/go#58547
Change-Id: I9de0ef5e482534b8733ef66363bac8f6c0fd3173
Reviewed-on: https://go-review.googlesource.com/c/net/+/498295
Run-TryBot: Damien Neil <dneil@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/quic/sent_val.go b/internal/quic/sent_val.go
new file mode 100644
index 0000000..f1a9c9f
--- /dev/null
+++ b/internal/quic/sent_val.go
@@ -0,0 +1,103 @@
+// 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
+
+// A sentVal tracks sending some piece of information to the peer.
+// It tracks whether the information has been sent, acked, and
+// (when in-flight) the most recent packet to carry it.
+//
+// For example, a sentVal can track sending of a RESET_STREAM frame.
+//
+// - unset: stream is active, no need to send RESET_STREAM
+// - unsent: we should send a RESET_STREAM, but have not yet
+// - sent: we have sent a RESET_STREAM, but have not received an ack
+// - received: we have sent a RESET_STREAM, and the peer has acked the packet that contained it
+//
+// In the "sent" state, a sentVal also tracks the latest packet number to carry
+// the information. (QUIC packet numbers are always at most 62 bits in size,
+// so the sentVal keeps the number in the low 62 bits and the state in the high 2 bits.)
+type sentVal uint64
+
+const (
+ sentValUnset = 0 // unset
+ sentValUnsent = 1 << 62 // set, not sent to the peer
+ sentValSent = 2 << 62 // set, sent to the peer but not yet acked; pnum is set
+ sentValReceived = 3 << 62 // set, peer acked receipt
+
+ sentValStateMask = 3 << 62
+)
+
+// isSet reports whether the value is set.
+func (s sentVal) isSet() bool { return s != 0 }
+
+// shouldSend reports whether the value is set and has not been sent to the peer.
+func (s sentVal) shouldSend() bool { return s.state() == sentValUnsent }
+
+// shouldSend reports whether the the value needs to be sent to the peer.
+// The value needs to be sent if it is set and has not been sent.
+// If pto is true, indicating that we are sending a PTO probe, the value
+// should also be sent if it is set and has not been acknowledged.
+func (s sentVal) shouldSendPTO(pto bool) bool {
+ st := s.state()
+ return st == sentValUnsent || (pto && st == sentValSent)
+}
+
+// isReceived reports whether the value has been received by the peer.
+func (s sentVal) isReceived() bool { return s == sentValReceived }
+
+// set sets the value and records that it should be sent to the peer.
+// If the value has already been sent, it is not resent.
+func (s *sentVal) set() {
+ if *s == 0 {
+ *s = sentValUnsent
+ }
+}
+
+// reset sets the value to the unsent state.
+func (s *sentVal) setUnsent() { *s = sentValUnsent }
+
+// clear sets the value to the unset state.
+func (s *sentVal) clear() { *s = sentValUnset }
+
+// setSent sets the value to the send state and records the number of the most recent
+// packet containing the value.
+func (s *sentVal) setSent(pnum packetNumber) {
+ *s = sentValSent | sentVal(pnum)
+}
+
+// setReceived sets the value to the received state.
+func (s *sentVal) setReceived() { *s = sentValReceived }
+
+// ackOrLoss reports that an acknowledgement has been received for the value,
+// or (if acked is false) that the packet carrying the value has been lost.
+func (s *sentVal) ackOrLoss(pnum packetNumber, acked bool) {
+ if acked {
+ *s = sentValReceived
+ } else if *s == sentVal(pnum)|sentValSent {
+ *s = sentValUnsent
+ }
+}
+
+// ackLatestOrLoss reports that an acknowledgement has been received for the value,
+// or (if acked is false) that the packet carrying the value has been lost.
+// The value is set to the acked state only if pnum is the latest packet containing it.
+//
+// We use this to handle acks for data that varies every time it is sent.
+// For example, if we send a MAX_DATA frame followed by an updated MAX_DATA value in a
+// second packet, we consider the data sent only upon receiving an ack for the most
+// recent value.
+func (s *sentVal) ackLatestOrLoss(pnum packetNumber, acked bool) {
+ if acked {
+ if *s == sentVal(pnum)|sentValSent {
+ *s = sentValReceived
+ }
+ } else {
+ if *s == sentVal(pnum)|sentValSent {
+ *s = sentValUnsent
+ }
+ }
+}
+
+func (s sentVal) state() uint64 { return uint64(s) & sentValStateMask }
diff --git a/internal/quic/sent_val_test.go b/internal/quic/sent_val_test.go
new file mode 100644
index 0000000..458b221
--- /dev/null
+++ b/internal/quic/sent_val_test.go
@@ -0,0 +1,166 @@
+// 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 TestSentVal(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ f func(*sentVal)
+ wantIsSet bool
+ wantShouldSend bool
+ wantIsReceived bool
+ wantShouldSendPTO bool
+ }{{
+ name: "zero value",
+ f: func(*sentVal) {},
+ wantIsSet: false,
+ wantShouldSend: false,
+ wantShouldSendPTO: false,
+ wantIsReceived: false,
+ }, {
+ name: "v.set()",
+ f: (*sentVal).set,
+ wantIsSet: true,
+ wantShouldSend: true,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "v.setSent(0)",
+ f: func(v *sentVal) {
+ v.setSent(0)
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "sent.set()",
+ f: func(v *sentVal) {
+ v.setSent(0)
+ v.set()
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "sent.setUnsent()",
+ f: func(v *sentVal) {
+ v.setSent(0)
+ v.setUnsent()
+ },
+ wantIsSet: true,
+ wantShouldSend: true,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "set.clear()",
+ f: func(v *sentVal) {
+ v.set()
+ v.clear()
+ },
+ wantIsSet: false,
+ wantShouldSend: false,
+ wantShouldSendPTO: false,
+ wantIsReceived: false,
+ }, {
+ name: "v.setReceived()",
+ f: (*sentVal).setReceived,
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: false,
+ wantIsReceived: true,
+ }, {
+ name: "v.ackOrLoss(!pnum, true)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackOrLoss(0, true) // ack different packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: false,
+ wantIsReceived: true,
+ }, {
+ name: "v.ackOrLoss(!pnum, false)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackOrLoss(0, false) // lose different packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "v.ackOrLoss(pnum, false)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackOrLoss(1, false) // lose same packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: true,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "v.ackLatestOrLoss(!pnum, true)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackLatestOrLoss(0, true) // ack different packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "v.ackLatestOrLoss(pnum, true)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackLatestOrLoss(1, true) // ack same packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: false,
+ wantIsReceived: true,
+ }, {
+ name: "v.ackLatestOrLoss(!pnum, false)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackLatestOrLoss(0, false) // lose different packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: false,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }, {
+ name: "v.ackLatestOrLoss(pnum, false)",
+ f: func(v *sentVal) {
+ v.setSent(1)
+ v.ackLatestOrLoss(1, false) // lose same packet containing the val
+ },
+ wantIsSet: true,
+ wantShouldSend: true,
+ wantShouldSendPTO: true,
+ wantIsReceived: false,
+ }} {
+ var v sentVal
+ test.f(&v)
+ if got, want := v.isSet(), test.wantIsSet; got != want {
+ t.Errorf("%v: v.isSet() = %v, want %v", test.name, got, want)
+ }
+ if got, want := v.shouldSend(), test.wantShouldSend; got != want {
+ t.Errorf("%v: v.shouldSend() = %v, want %v", test.name, got, want)
+ }
+ if got, want := v.shouldSendPTO(false), test.wantShouldSend; got != want {
+ t.Errorf("%v: v.shouldSendPTO(false) = %v, want %v", test.name, got, want)
+ }
+ if got, want := v.shouldSendPTO(true), test.wantShouldSendPTO; got != want {
+ t.Errorf("%v: v.shouldSendPTO(true) = %v, want %v", test.name, got, want)
+ }
+ if got, want := v.isReceived(), test.wantIsReceived; got != want {
+ t.Errorf("%v: v.isReceived() = %v, want %v", test.name, got, want)
+ }
+ }
+}