diff --git a/internal/quic/sent_val.go b/internal/quic/sent_val.go new file mode 100644 index 0000000000..f1a9c9fbc9 --- /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 0000000000..458b221c2a --- /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) + } + } +}