Files
Frodding/raknet/session.go
cubexteam b16628dd5d fix: splitSeq, map struct{}, unlock before WriteToUDP
fix: splitSeq, map struct{}, unlock before WriteToUDP
2026-03-04 18:16:16 +03:00

240 lines
4.7 KiB
Go

package raknet
import (
"net"
"sync"
"time"
)
type SessionState int
const (
StateHandshaking SessionState = iota
StateConnected
StateDisconnected
)
type Session struct {
mu sync.Mutex
Addr *net.UDPAddr
GUID int64
MTU uint16
conn *net.UDPConn
state SessionState
sendSeq uint32
recvSeq uint32
msgIndex uint32
splitSeq uint16
orderIndex [MaxChannels]uint32
orderNext [MaxChannels]uint32
orderQueues [MaxChannels]map[uint32][]byte
recvSeqs map[uint32]struct{}
pendingACK []uint32
pendingNACK []uint32
splitMap map[uint16]*splitBuf
recoveryMap map[uint32]*Datagram
lastRecv time.Time
connTime time.Time
}
type splitBuf struct {
count uint32
parts map[uint32][]byte
}
func NewSession(conn *net.UDPConn, addr *net.UDPAddr, guid int64, mtu uint16) *Session {
s := &Session{
conn: conn,
Addr: addr,
GUID: guid,
MTU: mtu,
state: StateHandshaking,
recvSeqs: make(map[uint32]struct{}),
splitMap: make(map[uint16]*splitBuf),
recoveryMap: make(map[uint32]*Datagram),
lastRecv: time.Now(),
}
for i := range s.orderQueues {
s.orderQueues[i] = make(map[uint32][]byte)
}
return s
}
func (s *Session) IsConnected() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.state == StateConnected
}
func (s *Session) IsDisconnected() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.state == StateDisconnected
}
func (s *Session) SetConnected() {
s.mu.Lock()
defer s.mu.Unlock()
s.state = StateConnected
s.connTime = time.Now()
}
func (s *Session) Timestamp() int64 {
if s.connTime.IsZero() {
return 0
}
return time.Since(s.connTime).Milliseconds()
}
func (s *Session) HandleACK(data []byte) {
nums, err := DecodeACKNACK(data)
if err != nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
for _, n := range nums {
delete(s.recoveryMap, n)
}
}
func (s *Session) HandleNACK(data []byte) {
nums, err := DecodeACKNACK(data)
if err != nil {
return
}
s.mu.Lock()
toResend := make([]*Datagram, 0, len(nums))
for _, n := range nums {
if dg, ok := s.recoveryMap[n]; ok {
toResend = append(toResend, dg)
}
}
s.mu.Unlock()
for _, dg := range toResend {
_, _ = s.conn.WriteToUDP(dg.Encode(), s.Addr)
}
}
func (s *Session) FlushACKs() {
s.mu.Lock()
acks := s.pendingACK
nacks := s.pendingNACK
s.pendingACK = nil
s.pendingNACK = nil
s.mu.Unlock()
if len(acks) > 0 {
_, _ = s.conn.WriteToUDP(EncodeACK(acks), s.Addr)
}
if len(nacks) > 0 {
_, _ = s.conn.WriteToUDP(EncodeNACK(nacks), s.Addr)
}
}
func (s *Session) Send(payload []byte, reliability Reliability, orderChannel byte) {
s.mu.Lock()
defer s.mu.Unlock()
maxPayload := int(s.MTU) - 4 - 3
if reliability.IsReliable() {
maxPayload -= 3
}
if reliability.IsOrdered() || reliability.IsSequenced() {
maxPayload -= 4
}
if maxPayload < 100 {
maxPayload = 100
}
if len(payload) <= maxPayload {
f := &Frame{Reliability: reliability, Payload: payload}
if reliability.IsReliable() {
f.MessageIndex = s.msgIndex
s.msgIndex++
}
if reliability.IsOrdered() {
f.OrderIndex = s.orderIndex[orderChannel]
f.OrderChannel = orderChannel
s.orderIndex[orderChannel]++
}
s.sendDatagram([]*Frame{f})
return
}
splitID := s.splitSeq
s.splitSeq++
count := (len(payload) + maxPayload - 1) / maxPayload
orderIdx := s.orderIndex[orderChannel]
if reliability.IsOrdered() {
s.orderIndex[orderChannel]++
}
for i := 0; i < count; i++ {
start := i * maxPayload
end := start + maxPayload
if end > len(payload) {
end = len(payload)
}
chunk := make([]byte, end-start)
copy(chunk, payload[start:end])
f := &Frame{
Reliability: reliability,
Split: true,
SplitCount: uint32(count),
SplitID: splitID,
SplitIndex: uint32(i),
Payload: chunk,
}
if reliability.IsReliable() {
f.MessageIndex = s.msgIndex
s.msgIndex++
}
if reliability.IsOrdered() {
f.OrderIndex = orderIdx
f.OrderChannel = orderChannel
}
s.sendDatagram([]*Frame{f})
}
}
func (s *Session) sendDatagram(frames []*Frame) {
seq := s.sendSeq
s.sendSeq++
dg := &Datagram{SeqNum: seq, Frames: frames}
_, _ = s.conn.WriteToUDP(dg.Encode(), s.Addr)
for _, f := range frames {
if f.Reliability.IsReliable() {
s.recoveryMap[seq] = dg
break
}
}
}
func (s *Session) SendRaw(data []byte) {
_, _ = s.conn.WriteToUDP(data, s.Addr)
}
func (s *Session) TimedOut() bool {
s.mu.Lock()
defer s.mu.Unlock()
return time.Since(s.lastRecv) > SessionTimeout
}
func (s *Session) Disconnect() {
s.mu.Lock()
s.state = StateDisconnected
seq := s.sendSeq
s.sendSeq++
s.mu.Unlock()
f := &Frame{Reliability: Unreliable, Payload: []byte{0x15}}
dg := &Datagram{SeqNum: seq, Frames: []*Frame{f}}
_, _ = s.conn.WriteToUDP(dg.Encode(), s.Addr)
}