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)
+		}
+	}
+}