feat(gateway): repair native Apollo provider path
This commit is contained in:
@@ -0,0 +1,709 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
apolloENetChannels = 48
|
||||
apolloENetMaximumPacket = 4096
|
||||
apolloENetMaximumPayload = 2048
|
||||
apolloENetMaximumCommands = 32
|
||||
apolloENetMaximumPending = 128
|
||||
apolloENetMaximumReorder = 64
|
||||
// These are the pinned ENet fork defaults. The connection remains subject
|
||||
// to the stricter ten-second no-receive peer deadline below.
|
||||
apolloENetTimeoutLimit = 32
|
||||
apolloENetTimeoutMinimum = 5 * time.Second
|
||||
apolloENetTimeoutMaximum = 30 * time.Second
|
||||
apolloENetPeerTimeout = 10 * time.Second
|
||||
apolloENetPeerIDMask = 0x0fff
|
||||
apolloENetSentTimeFlag = 0x8000
|
||||
apolloENetCompressedFlag = 0x4000
|
||||
apolloENetSessionMask = 0x3000
|
||||
apolloENetSessionShift = 12
|
||||
apolloENetCommandMask = 0x0f
|
||||
apolloENetAcknowledged = 0x80
|
||||
apolloENetUnsequenced = 0x40
|
||||
apolloENetConnect = 2
|
||||
apolloENetVerifyConnect = 3
|
||||
apolloENetDisconnect = 4
|
||||
apolloENetPing = 5
|
||||
apolloENetSendReliable = 6
|
||||
apolloENetSendUnsequenced = 9
|
||||
apolloENetBandwidthLimit = 10
|
||||
apolloENetThrottleConfig = 11
|
||||
)
|
||||
|
||||
var errApolloENet = errors.New("apollo ENet malformed")
|
||||
|
||||
type apolloENetState uint8
|
||||
|
||||
const (
|
||||
apolloENetConnecting apolloENetState = iota
|
||||
apolloENetConnected
|
||||
apolloENetDisconnecting
|
||||
apolloENetClosed
|
||||
)
|
||||
|
||||
type apolloENetChannel struct {
|
||||
nextOutgoing uint16
|
||||
lastIncoming uint16
|
||||
hasIncoming bool
|
||||
incoming map[uint16][]byte
|
||||
}
|
||||
|
||||
type apolloENetPendingKey struct {
|
||||
channel uint8
|
||||
sequence uint16
|
||||
}
|
||||
|
||||
type apolloENetPending struct {
|
||||
packet []byte
|
||||
firstSent time.Time
|
||||
sentTime time.Time
|
||||
timeout time.Duration
|
||||
attempts uint8
|
||||
}
|
||||
|
||||
// apolloENetPeer is deliberately scoped to the Apollo adapter. It implements
|
||||
// one negotiated ENet peer over one connected UDP socket and exposes no
|
||||
// reusable transport abstraction.
|
||||
type apolloENetPeer struct {
|
||||
conn *net.UDPConn
|
||||
now func() time.Time
|
||||
|
||||
mu sync.Mutex
|
||||
state apolloENetState
|
||||
peerID uint16
|
||||
inboundSession uint8
|
||||
outboundSession uint8
|
||||
connectID uint32
|
||||
channels [apolloENetChannels]apolloENetChannel
|
||||
pending map[apolloENetPendingKey]*apolloENetPending
|
||||
unsequenced uint16
|
||||
rtt time.Duration
|
||||
variance time.Duration
|
||||
reliableSent uint64
|
||||
retransmits uint64
|
||||
lastReceive time.Time
|
||||
lastSend time.Time
|
||||
lastPing time.Time
|
||||
disconnectAck chan struct{}
|
||||
disconnectSeq uint16
|
||||
onPayload func(uint8, bool, []byte)
|
||||
onDisconnect func(error)
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func newApolloENetPeer(conn *net.UDPConn, now func() time.Time) (*apolloENetPeer, error) {
|
||||
if conn == nil {
|
||||
return nil, ErrProviderMalformed
|
||||
}
|
||||
if now == nil {
|
||||
now = time.Now
|
||||
}
|
||||
return &apolloENetPeer{
|
||||
conn: conn, now: now, state: apolloENetConnecting, pending: make(map[apolloENetPendingKey]*apolloENetPending),
|
||||
rtt: 500 * time.Millisecond, variance: time.Millisecond, done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) Connect(ctx context.Context, connectData uint32) error {
|
||||
if p == nil || connectData == 0 {
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
var id [4]byte
|
||||
if _, err := rand.Read(id[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
p.mu.Lock()
|
||||
if p.state != apolloENetConnecting {
|
||||
p.mu.Unlock()
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
p.connectID = binary.BigEndian.Uint32(id[:])
|
||||
now := p.now()
|
||||
packet := apolloENetConnectPacket(now, p.connectID, connectData)
|
||||
p.pending[apolloENetPendingKey{channel: 0xff, sequence: 1}] = &apolloENetPending{packet: append([]byte(nil), packet...), firstSent: now, sentTime: now, timeout: apolloENetRetransmitTimeout(p.rtt, p.variance, 1), attempts: 1}
|
||||
err := p.writeLocked(packet)
|
||||
p.mu.Unlock()
|
||||
if err != nil {
|
||||
p.close(err)
|
||||
return err
|
||||
}
|
||||
for {
|
||||
if err := p.readOnce(ctx); err != nil {
|
||||
p.close(err)
|
||||
return err
|
||||
}
|
||||
p.mu.Lock()
|
||||
connected := p.state == apolloENetConnected
|
||||
p.mu.Unlock()
|
||||
if connected {
|
||||
go p.run()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apolloENetConnectPacket(now time.Time, connectID, data uint32) []byte {
|
||||
packet := make([]byte, 52)
|
||||
apolloENetHeader(packet, apolloENetPeerIDMask, 0, now)
|
||||
packet[4] = apolloENetConnect | apolloENetAcknowledged
|
||||
packet[5] = 0xff
|
||||
binary.BigEndian.PutUint16(packet[6:8], 1)
|
||||
binary.BigEndian.PutUint16(packet[8:10], 0)
|
||||
packet[10], packet[11] = 0xff, 0xff
|
||||
binary.BigEndian.PutUint32(packet[12:16], 1400)
|
||||
binary.BigEndian.PutUint32(packet[16:20], 32768)
|
||||
binary.BigEndian.PutUint32(packet[20:24], apolloENetChannels)
|
||||
binary.BigEndian.PutUint32(packet[24:28], 0)
|
||||
binary.BigEndian.PutUint32(packet[28:32], 0)
|
||||
binary.BigEndian.PutUint32(packet[32:36], 5000)
|
||||
binary.BigEndian.PutUint32(packet[36:40], 2)
|
||||
binary.BigEndian.PutUint32(packet[40:44], 2)
|
||||
binary.BigEndian.PutUint32(packet[44:48], connectID)
|
||||
binary.BigEndian.PutUint32(packet[48:52], data)
|
||||
return packet
|
||||
}
|
||||
|
||||
func apolloENetHeader(packet []byte, peerID uint16, session uint8, now time.Time) {
|
||||
value := peerID&apolloENetPeerIDMask | (uint16(session&3) << apolloENetSessionShift) | apolloENetSentTimeFlag
|
||||
binary.BigEndian.PutUint16(packet[:2], value)
|
||||
binary.BigEndian.PutUint16(packet[2:4], uint16(now.UnixMilli()))
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) run() {
|
||||
ticker := time.NewTicker(25 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-p.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := p.maintain(); err != nil {
|
||||
p.close(err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
err := p.readOnce(ctx)
|
||||
cancel()
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) && !isApolloENetTimeout(err) {
|
||||
p.close(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) readOnce(ctx context.Context) error {
|
||||
if p == nil || p.conn == nil {
|
||||
return ErrProviderDisconnected
|
||||
}
|
||||
deadline := time.Now().Add(100 * time.Millisecond)
|
||||
if contextDeadline, ok := ctx.Deadline(); ok && contextDeadline.Before(deadline) {
|
||||
deadline = contextDeadline
|
||||
}
|
||||
if err := p.conn.SetReadDeadline(deadline); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := make([]byte, apolloENetMaximumPacket+1)
|
||||
count, err := p.conn.Read(buffer)
|
||||
if err != nil {
|
||||
if networkErr, ok := err.(net.Error); ok && networkErr.Timeout() {
|
||||
return context.DeadlineExceeded
|
||||
}
|
||||
return err
|
||||
}
|
||||
if count < 4 || count > apolloENetMaximumPacket {
|
||||
return errApolloENet
|
||||
}
|
||||
return p.handleDatagram(buffer[:count])
|
||||
}
|
||||
|
||||
func isApolloENetTimeout(err error) bool {
|
||||
return errors.Is(err, context.DeadlineExceeded)
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) handleDatagram(packet []byte) error {
|
||||
if len(packet) < 4 || len(packet) > apolloENetMaximumPacket {
|
||||
return errApolloENet
|
||||
}
|
||||
header := binary.BigEndian.Uint16(packet[:2])
|
||||
if header&apolloENetCompressedFlag != 0 {
|
||||
return errApolloENet
|
||||
}
|
||||
peerID := header & apolloENetPeerIDMask
|
||||
session := uint8((header & apolloENetSessionMask) >> apolloENetSessionShift)
|
||||
offset := 2
|
||||
sentTime := uint16(0)
|
||||
if header&apolloENetSentTimeFlag != 0 {
|
||||
if len(packet) < 4 {
|
||||
return errApolloENet
|
||||
}
|
||||
sentTime = binary.BigEndian.Uint16(packet[2:4])
|
||||
offset = 4
|
||||
}
|
||||
p.mu.Lock()
|
||||
state := p.state
|
||||
if state == apolloENetClosed || (state == apolloENetConnected && (peerID != p.peerID || session != p.inboundSession)) {
|
||||
p.mu.Unlock()
|
||||
return errApolloENet
|
||||
}
|
||||
p.lastReceive = p.now()
|
||||
p.mu.Unlock()
|
||||
commands := 0
|
||||
for offset < len(packet) {
|
||||
commands++
|
||||
if commands > apolloENetMaximumCommands || len(packet)-offset < 4 {
|
||||
return errApolloENet
|
||||
}
|
||||
command := packet[offset] & apolloENetCommandMask
|
||||
flags := packet[offset]
|
||||
channel := packet[offset+1]
|
||||
sequence := binary.BigEndian.Uint16(packet[offset+2 : offset+4])
|
||||
consumed, err := p.handleCommand(command, flags, channel, sequence, sentTime, packet[offset:])
|
||||
if err != nil || consumed < 4 || consumed > len(packet)-offset {
|
||||
return errApolloENet
|
||||
}
|
||||
offset += consumed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) handleCommand(command, flags, channel uint8, sequence, sentTime uint16, data []byte) (int, error) {
|
||||
switch command {
|
||||
case 1:
|
||||
if len(data) < 8 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
return 8, p.acknowledge(channel, binary.BigEndian.Uint16(data[4:6]), binary.BigEndian.Uint16(data[6:8]))
|
||||
case apolloENetVerifyConnect:
|
||||
if len(data) < 44 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
return 44, p.verifyConnect(sequence, sentTime, data[:44])
|
||||
case apolloENetDisconnect:
|
||||
if len(data) < 8 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
if flags&apolloENetAcknowledged != 0 {
|
||||
if err := p.sendAcknowledge(channel, sequence, sentTime); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 8, ErrProviderDisconnected
|
||||
case apolloENetPing:
|
||||
if flags&apolloENetAcknowledged != 0 {
|
||||
if err := p.sendAcknowledge(channel, sequence, sentTime); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 4, nil
|
||||
case apolloENetSendReliable:
|
||||
if len(data) < 6 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(data[4:6]))
|
||||
if length > apolloENetMaximumPayload || len(data) < 6+length {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
if flags&apolloENetAcknowledged == 0 || channel >= apolloENetChannels {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
if err := p.sendAcknowledge(channel, sequence, sentTime); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
deliver, err := p.acceptReliable(channel, sequence, data[6:6+length])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, message := range deliver {
|
||||
p.deliver(channel, true, message)
|
||||
}
|
||||
return 6 + length, nil
|
||||
case apolloENetSendUnsequenced:
|
||||
if len(data) < 8 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
length := int(binary.BigEndian.Uint16(data[6:8]))
|
||||
if flags&apolloENetUnsequenced == 0 || channel >= apolloENetChannels || length > apolloENetMaximumPayload || len(data) < 8+length {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
p.deliver(channel, false, data[8:8+length])
|
||||
return 8 + length, nil
|
||||
case apolloENetBandwidthLimit:
|
||||
if len(data) < 12 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
return 12, nil
|
||||
case apolloENetThrottleConfig:
|
||||
if len(data) < 16 {
|
||||
return 0, errApolloENet
|
||||
}
|
||||
return 16, nil
|
||||
case apolloENetConnect, 7, 8, 12:
|
||||
return 0, errApolloENet
|
||||
default:
|
||||
return 0, errApolloENet
|
||||
}
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) verifyConnect(sequence, sentTime uint16, data []byte) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.state != apolloENetConnecting || binary.BigEndian.Uint32(data[40:44]) != p.connectID || binary.BigEndian.Uint32(data[16:20]) != apolloENetChannels {
|
||||
return errApolloENet
|
||||
}
|
||||
p.peerID = binary.BigEndian.Uint16(data[4:6])
|
||||
p.inboundSession = data[6]
|
||||
p.outboundSession = data[7]
|
||||
p.state = apolloENetConnected
|
||||
delete(p.pending, apolloENetPendingKey{channel: 0xff, sequence: 1})
|
||||
return p.sendAcknowledgeLocked(0xff, sequence, sentTime)
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) acknowledge(channel uint8, sequence, sentTime uint16) error {
|
||||
p.mu.Lock()
|
||||
if p.state == apolloENetDisconnecting && channel == 0xff && sequence == p.disconnectSeq && p.disconnectAck != nil {
|
||||
close(p.disconnectAck)
|
||||
p.disconnectAck = nil
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
key := apolloENetPendingKey{channel: channel, sequence: sequence}
|
||||
pending, ok := p.pending[key]
|
||||
if !ok {
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
delete(p.pending, key)
|
||||
measured := p.now().Sub(pending.sentTime)
|
||||
if measured < 0 || measured > 30*time.Second {
|
||||
p.mu.Unlock()
|
||||
return errApolloENet
|
||||
}
|
||||
delta := durationAbs(p.rtt - measured)
|
||||
p.variance += (delta - p.variance) / 4
|
||||
p.rtt += (measured - p.rtt) / 8
|
||||
p.mu.Unlock()
|
||||
_ = sentTime
|
||||
return nil
|
||||
}
|
||||
|
||||
func durationAbs(value time.Duration) time.Duration {
|
||||
if value < 0 {
|
||||
return -value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) acceptReliable(channel uint8, sequence uint16, payload []byte) ([][]byte, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
state := &p.channels[channel]
|
||||
if !state.hasIncoming {
|
||||
state.hasIncoming = true
|
||||
state.lastIncoming = sequence
|
||||
return [][]byte{append([]byte(nil), payload...)}, nil
|
||||
}
|
||||
if sequence == state.lastIncoming+1 {
|
||||
state.lastIncoming = sequence
|
||||
deliver := [][]byte{append([]byte(nil), payload...)}
|
||||
for {
|
||||
next := state.lastIncoming + 1
|
||||
queued, ok := state.incoming[next]
|
||||
if !ok {
|
||||
return deliver, nil
|
||||
}
|
||||
delete(state.incoming, next)
|
||||
state.lastIncoming = next
|
||||
deliver = append(deliver, queued)
|
||||
}
|
||||
}
|
||||
if apolloENetSequenceGreater(sequence, state.lastIncoming) {
|
||||
if uint16(sequence-state.lastIncoming) > 1024 || len(state.incoming) >= apolloENetMaximumReorder {
|
||||
return nil, errApolloENet
|
||||
}
|
||||
if state.incoming == nil {
|
||||
state.incoming = make(map[uint16][]byte)
|
||||
}
|
||||
if _, duplicate := state.incoming[sequence]; !duplicate {
|
||||
state.incoming[sequence] = append([]byte(nil), payload...)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func apolloENetSequenceGreater(first, second uint16) bool {
|
||||
return (first > second && first-second <= 32768) || (first < second && second-first > 32768)
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) deliver(channel uint8, reliable bool, payload []byte) {
|
||||
p.mu.Lock()
|
||||
callback := p.onPayload
|
||||
p.mu.Unlock()
|
||||
if callback != nil {
|
||||
callback(channel, reliable, append([]byte(nil), payload...))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) SendReliable(channel uint8, payload []byte) error {
|
||||
if channel >= apolloENetChannels || len(payload) == 0 || len(payload) > apolloENetMaximumPayload {
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.state != apolloENetConnected || len(p.pending) >= apolloENetMaximumPending {
|
||||
return ErrProviderDisconnected
|
||||
}
|
||||
state := &p.channels[channel]
|
||||
state.nextOutgoing++
|
||||
if state.nextOutgoing == 0 {
|
||||
state.nextOutgoing++
|
||||
}
|
||||
now := p.now()
|
||||
packet := make([]byte, 10+len(payload))
|
||||
apolloENetHeader(packet, p.peerID, p.outboundSession, now)
|
||||
packet[4] = apolloENetSendReliable | apolloENetAcknowledged
|
||||
packet[5] = channel
|
||||
binary.BigEndian.PutUint16(packet[6:8], state.nextOutgoing)
|
||||
binary.BigEndian.PutUint16(packet[8:10], uint16(len(payload)))
|
||||
copy(packet[10:], payload)
|
||||
key := apolloENetPendingKey{channel: channel, sequence: state.nextOutgoing}
|
||||
p.pending[key] = &apolloENetPending{packet: append([]byte(nil), packet...), firstSent: now, sentTime: now, timeout: apolloENetRetransmitTimeout(p.rtt, p.variance, 1), attempts: 1}
|
||||
if err := p.writeLocked(packet); err != nil {
|
||||
delete(p.pending, key)
|
||||
return err
|
||||
}
|
||||
p.reliableSent++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) SendUnsequenced(channel uint8, payload []byte) error {
|
||||
if channel >= apolloENetChannels || len(payload) == 0 || len(payload) > apolloENetMaximumPayload {
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.state != apolloENetConnected {
|
||||
return ErrProviderDisconnected
|
||||
}
|
||||
p.unsequenced++
|
||||
packet := make([]byte, 12+len(payload))
|
||||
apolloENetHeader(packet, p.peerID, p.outboundSession, p.now())
|
||||
packet[4] = apolloENetSendUnsequenced | apolloENetUnsequenced
|
||||
packet[5] = channel
|
||||
binary.BigEndian.PutUint16(packet[6:8], 0)
|
||||
binary.BigEndian.PutUint16(packet[8:10], p.unsequenced)
|
||||
binary.BigEndian.PutUint16(packet[10:12], uint16(len(payload)))
|
||||
copy(packet[12:], payload)
|
||||
return p.writeLocked(packet)
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) sendAcknowledge(channel uint8, sequence, sentTime uint16) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.sendAcknowledgeLocked(channel, sequence, sentTime)
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) sendAcknowledgeLocked(channel uint8, sequence, sentTime uint16) error {
|
||||
if p.state == apolloENetClosed {
|
||||
return ErrProviderDisconnected
|
||||
}
|
||||
packet := make([]byte, 12)
|
||||
peerID := p.peerID
|
||||
session := p.outboundSession
|
||||
if p.state == apolloENetConnecting {
|
||||
peerID, session = apolloENetPeerIDMask, 0
|
||||
}
|
||||
apolloENetHeader(packet, peerID, session, p.now())
|
||||
packet[4] = 1
|
||||
packet[5] = channel
|
||||
binary.BigEndian.PutUint16(packet[6:8], 0)
|
||||
binary.BigEndian.PutUint16(packet[8:10], sequence)
|
||||
binary.BigEndian.PutUint16(packet[10:12], sentTime)
|
||||
return p.writeLocked(packet)
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) maintain() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.state != apolloENetConnected && p.state != apolloENetDisconnecting {
|
||||
return nil
|
||||
}
|
||||
now := p.now()
|
||||
if !p.lastReceive.IsZero() && now.Sub(p.lastReceive) > apolloENetPeerTimeout {
|
||||
return ErrProviderTimeout
|
||||
}
|
||||
for key, pending := range p.pending {
|
||||
if now.Sub(pending.sentTime) < pending.timeout {
|
||||
continue
|
||||
}
|
||||
if now.Sub(pending.firstSent) >= apolloENetTimeoutMaximum || (apolloENetExceededTimeoutLimit(pending.attempts) && now.Sub(pending.firstSent) >= apolloENetTimeoutMinimum) {
|
||||
return ErrProviderTimeout
|
||||
}
|
||||
pending.timeout = apolloENetRetransmitTimeout(p.rtt, p.variance, pending.attempts)
|
||||
pending.attempts++
|
||||
p.retransmits++
|
||||
pending.sentTime = now
|
||||
binary.BigEndian.PutUint16(pending.packet[2:4], uint16(now.UnixMilli()))
|
||||
if err := p.writeLocked(pending.packet); err != nil {
|
||||
return err
|
||||
}
|
||||
p.pending[key] = pending
|
||||
}
|
||||
if now.Sub(p.lastPing) >= 500*time.Millisecond {
|
||||
p.lastPing = now
|
||||
return p.sendPingLocked()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func apolloENetRetransmitTimeout(rtt, variance time.Duration, attempts uint8) time.Duration {
|
||||
if rtt < time.Millisecond {
|
||||
rtt = time.Millisecond
|
||||
}
|
||||
if variance < time.Millisecond {
|
||||
variance = time.Millisecond
|
||||
}
|
||||
base := rtt + minDuration(rtt, 4*variance)
|
||||
if base > apolloENetTimeoutMaximum/5 {
|
||||
base = apolloENetTimeoutMaximum / 5
|
||||
}
|
||||
if attempts == 0 {
|
||||
attempts = 1
|
||||
}
|
||||
if attempts > apolloENetTimeoutLimit {
|
||||
attempts = apolloENetTimeoutLimit
|
||||
}
|
||||
return base * time.Duration(attempts)
|
||||
}
|
||||
|
||||
func apolloENetExceededTimeoutLimit(attempts uint8) bool {
|
||||
return attempts >= 6 // 1 << (attempts - 1) reaches the fork's limit of 32.
|
||||
}
|
||||
|
||||
func minDuration(first, second time.Duration) time.Duration {
|
||||
if first < second {
|
||||
return first
|
||||
}
|
||||
return second
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) sendPingLocked() error {
|
||||
if len(p.pending) >= apolloENetMaximumPending {
|
||||
return ErrProviderTimeout
|
||||
}
|
||||
state := &p.channels[0]
|
||||
state.nextOutgoing++
|
||||
if state.nextOutgoing == 0 {
|
||||
state.nextOutgoing++
|
||||
}
|
||||
now := p.now()
|
||||
packet := make([]byte, 8)
|
||||
apolloENetHeader(packet, p.peerID, p.outboundSession, now)
|
||||
packet[4] = apolloENetPing | apolloENetAcknowledged
|
||||
packet[5] = 0
|
||||
binary.BigEndian.PutUint16(packet[6:8], state.nextOutgoing)
|
||||
p.pending[apolloENetPendingKey{channel: 0, sequence: state.nextOutgoing}] = &apolloENetPending{packet: append([]byte(nil), packet...), firstSent: now, sentTime: now, timeout: apolloENetRetransmitTimeout(p.rtt, p.variance, 1), attempts: 1}
|
||||
if err := p.writeLocked(packet); err != nil {
|
||||
delete(p.pending, apolloENetPendingKey{channel: 0, sequence: state.nextOutgoing})
|
||||
return err
|
||||
}
|
||||
p.reliableSent++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) telemetry() ProviderTelemetry {
|
||||
if p == nil {
|
||||
return ProviderTelemetry{}
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return ProviderTelemetry{
|
||||
ControlRTT: p.rtt, ControlJitter: p.variance, ReliableSent: p.reliableSent,
|
||||
ReliableRetransmits: p.retransmits, PendingReliable: uint64(len(p.pending)),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) writeLocked(packet []byte) error {
|
||||
if len(packet) < 4 || len(packet) > apolloENetMaximumPacket {
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
count, err := p.conn.Write(packet)
|
||||
if err != nil || count != len(packet) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrProviderDisconnected
|
||||
}
|
||||
p.lastSend = p.now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) Disconnect(ctx context.Context) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
if p.state == apolloENetClosed {
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
p.state = apolloENetDisconnecting
|
||||
ack := make(chan struct{})
|
||||
p.disconnectAck, p.disconnectSeq = ack, 1
|
||||
packet := make([]byte, 12)
|
||||
apolloENetHeader(packet, p.peerID, p.outboundSession, p.now())
|
||||
packet[4] = apolloENetDisconnect | apolloENetAcknowledged
|
||||
packet[5] = 0xff
|
||||
binary.BigEndian.PutUint16(packet[6:8], 1)
|
||||
if err := p.writeLocked(packet); err != nil {
|
||||
p.mu.Unlock()
|
||||
p.close(err)
|
||||
return err
|
||||
}
|
||||
p.mu.Unlock()
|
||||
deadline := time.NewTimer(2 * time.Second)
|
||||
defer deadline.Stop()
|
||||
select {
|
||||
case <-ack:
|
||||
p.close(nil)
|
||||
return nil
|
||||
case <-p.done:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
p.close(ctx.Err())
|
||||
return ctx.Err()
|
||||
case <-deadline.C:
|
||||
p.close(ErrProviderTimeout)
|
||||
return ErrProviderTimeout
|
||||
}
|
||||
}
|
||||
|
||||
func (p *apolloENetPeer) close(err error) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
p.closeOnce.Do(func() {
|
||||
p.mu.Lock()
|
||||
p.state = apolloENetClosed
|
||||
callback := p.onDisconnect
|
||||
p.mu.Unlock()
|
||||
close(p.done)
|
||||
_ = p.conn.Close()
|
||||
if callback != nil && err != nil {
|
||||
callback(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user