package quic
import (
"fmt"
"slices"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
)
type newConnID struct {
SequenceNumber uint64
ConnectionID protocol .ConnectionID
StatelessResetToken protocol .StatelessResetToken
}
type connIDManager struct {
queue []newConnID
highestProbingID uint64
pathProbing map [pathID ]newConnID
handshakeComplete bool
activeSequenceNumber uint64
highestRetired uint64
activeConnectionID protocol .ConnectionID
activeStatelessResetToken *protocol .StatelessResetToken
rand utils .Rand
packetsSinceLastChange uint32
packetsPerConnectionID uint32
addStatelessResetToken func (protocol .StatelessResetToken )
removeStatelessResetToken func (protocol .StatelessResetToken )
queueControlFrame func (wire .Frame )
closed bool
}
func newConnIDManager(
initialDestConnID protocol .ConnectionID ,
addStatelessResetToken func (protocol .StatelessResetToken ),
removeStatelessResetToken func (protocol .StatelessResetToken ),
queueControlFrame func (wire .Frame ),
) *connIDManager {
return &connIDManager {
activeConnectionID : initialDestConnID ,
addStatelessResetToken : addStatelessResetToken ,
removeStatelessResetToken : removeStatelessResetToken ,
queueControlFrame : queueControlFrame ,
queue : make ([]newConnID , 0 , protocol .MaxActiveConnectionIDs ),
}
}
func (h *connIDManager ) AddFromPreferredAddress (connID protocol .ConnectionID , resetToken protocol .StatelessResetToken ) error {
return h .addConnectionID (1 , connID , resetToken )
}
func (h *connIDManager ) Add (f *wire .NewConnectionIDFrame ) error {
if err := h .add (f ); err != nil {
return err
}
if len (h .queue ) >= protocol .MaxActiveConnectionIDs {
return &qerr .TransportError {ErrorCode : qerr .ConnectionIDLimitError }
}
return nil
}
func (h *connIDManager ) add (f *wire .NewConnectionIDFrame ) error {
if h .activeConnectionID .Len () == 0 {
return &qerr .TransportError {
ErrorCode : qerr .ProtocolViolation ,
ErrorMessage : "received NEW_CONNECTION_ID frame but zero-length connection IDs are in use" ,
}
}
if f .SequenceNumber < max (h .activeSequenceNumber , h .highestProbingID ) || f .SequenceNumber < h .highestRetired {
h .queueControlFrame (&wire .RetireConnectionIDFrame {
SequenceNumber : f .SequenceNumber ,
})
return nil
}
if f .RetirePriorTo != 0 && h .pathProbing != nil {
for id , entry := range h .pathProbing {
if entry .SequenceNumber < f .RetirePriorTo {
h .queueControlFrame (&wire .RetireConnectionIDFrame {
SequenceNumber : entry .SequenceNumber ,
})
h .removeStatelessResetToken (entry .StatelessResetToken )
delete (h .pathProbing , id )
}
}
}
if f .RetirePriorTo > h .highestRetired {
var newQueue []newConnID
for _ , entry := range h .queue {
if entry .SequenceNumber >= f .RetirePriorTo {
newQueue = append (newQueue , entry )
} else {
h .queueControlFrame (&wire .RetireConnectionIDFrame {SequenceNumber : entry .SequenceNumber })
}
}
h .queue = newQueue
h .highestRetired = f .RetirePriorTo
}
if f .SequenceNumber == h .activeSequenceNumber {
return nil
}
if err := h .addConnectionID (f .SequenceNumber , f .ConnectionID , f .StatelessResetToken ); err != nil {
return err
}
if h .activeSequenceNumber < f .RetirePriorTo {
h .updateConnectionID ()
}
return nil
}
func (h *connIDManager ) addConnectionID (seq uint64 , connID protocol .ConnectionID , resetToken protocol .StatelessResetToken ) error {
if len (h .queue ) == 0 || h .queue [len (h .queue )-1 ].SequenceNumber < seq {
h .queue = append (h .queue , newConnID {
SequenceNumber : seq ,
ConnectionID : connID ,
StatelessResetToken : resetToken ,
})
return nil
}
for i , entry := range h .queue {
if entry .SequenceNumber == seq {
if entry .ConnectionID != connID {
return fmt .Errorf ("received conflicting connection IDs for sequence number %d" , seq )
}
if entry .StatelessResetToken != resetToken {
return fmt .Errorf ("received conflicting stateless reset tokens for sequence number %d" , seq )
}
return nil
}
if entry .SequenceNumber > seq {
h .queue = slices .Insert (h .queue , i , newConnID {
SequenceNumber : seq ,
ConnectionID : connID ,
StatelessResetToken : resetToken ,
})
return nil
}
}
return nil
}
func (h *connIDManager ) updateConnectionID () {
h .assertNotClosed ()
h .queueControlFrame (&wire .RetireConnectionIDFrame {
SequenceNumber : h .activeSequenceNumber ,
})
h .highestRetired = max (h .highestRetired , h .activeSequenceNumber )
if h .activeStatelessResetToken != nil {
h .removeStatelessResetToken (*h .activeStatelessResetToken )
}
front := h .queue [0 ]
h .queue = h .queue [1 :]
h .activeSequenceNumber = front .SequenceNumber
h .activeConnectionID = front .ConnectionID
h .activeStatelessResetToken = &front .StatelessResetToken
h .packetsSinceLastChange = 0
h .packetsPerConnectionID = protocol .PacketsPerConnectionID /2 + uint32 (h .rand .Int31n (protocol .PacketsPerConnectionID ))
h .addStatelessResetToken (*h .activeStatelessResetToken )
}
func (h *connIDManager ) Close () {
h .closed = true
if h .activeStatelessResetToken != nil {
h .removeStatelessResetToken (*h .activeStatelessResetToken )
}
if h .pathProbing != nil {
for _ , entry := range h .pathProbing {
h .removeStatelessResetToken (entry .StatelessResetToken )
}
}
}
func (h *connIDManager ) ChangeInitialConnID (newConnID protocol .ConnectionID ) {
if h .activeSequenceNumber != 0 {
panic ("expected first connection ID to have sequence number 0" )
}
h .activeConnectionID = newConnID
}
func (h *connIDManager ) SetStatelessResetToken (token protocol .StatelessResetToken ) {
h .assertNotClosed ()
if h .activeSequenceNumber != 0 {
panic ("expected first connection ID to have sequence number 0" )
}
h .activeStatelessResetToken = &token
h .addStatelessResetToken (token )
}
func (h *connIDManager ) SentPacket () {
h .packetsSinceLastChange ++
}
func (h *connIDManager ) shouldUpdateConnID () bool {
if !h .handshakeComplete {
return false
}
if len (h .queue ) > 0 && h .activeSequenceNumber == 0 {
return true
}
return 2 *len (h .queue ) >= protocol .MaxActiveConnectionIDs &&
h .packetsSinceLastChange >= h .packetsPerConnectionID
}
func (h *connIDManager ) Get () protocol .ConnectionID {
h .assertNotClosed ()
if h .shouldUpdateConnID () {
h .updateConnectionID ()
}
return h .activeConnectionID
}
func (h *connIDManager ) SetHandshakeComplete () {
h .handshakeComplete = true
}
func (h *connIDManager ) GetConnIDForPath (id pathID ) (protocol .ConnectionID , bool ) {
h .assertNotClosed ()
if h .activeConnectionID .Len () == 0 {
return protocol .ConnectionID {}, true
}
if h .pathProbing == nil {
h .pathProbing = make (map [pathID ]newConnID )
}
entry , ok := h .pathProbing [id ]
if ok {
return entry .ConnectionID , true
}
if len (h .queue ) == 0 {
return protocol .ConnectionID {}, false
}
front := h .queue [0 ]
h .queue = h .queue [1 :]
h .pathProbing [id ] = front
h .highestProbingID = front .SequenceNumber
h .addStatelessResetToken (front .StatelessResetToken )
return front .ConnectionID , true
}
func (h *connIDManager ) RetireConnIDForPath (pathID pathID ) {
h .assertNotClosed ()
if h .activeConnectionID .Len () == 0 {
return
}
entry , ok := h .pathProbing [pathID ]
if !ok {
return
}
h .queueControlFrame (&wire .RetireConnectionIDFrame {
SequenceNumber : entry .SequenceNumber ,
})
h .removeStatelessResetToken (entry .StatelessResetToken )
delete (h .pathProbing , pathID )
}
func (h *connIDManager ) IsActiveStatelessResetToken (token protocol .StatelessResetToken ) bool {
if h .activeStatelessResetToken != nil {
if *h .activeStatelessResetToken == token {
return true
}
}
if h .pathProbing != nil {
for _ , entry := range h .pathProbing {
if entry .StatelessResetToken == token {
return true
}
}
}
return false
}
func (h *connIDManager ) assertNotClosed () {
if h .closed {
panic ("connection ID manager is closed" )
}
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .