Skip to content

Commit

Permalink
quic: add a type tracking sent values
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Jonathan Amsterdam <[email protected]>
Auto-Submit: Damien Neil <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
neild authored and gopherbot committed May 25, 2023
1 parent 1b5a2d8 commit f7250ea
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 0 deletions.
103 changes: 103 additions & 0 deletions internal/quic/sent_val.go
Original file line number Diff line number Diff line change
@@ -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 }
166 changes: 166 additions & 0 deletions internal/quic/sent_val_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit f7250ea

Please sign in to comment.