506 lines
12 KiB
Go
506 lines
12 KiB
Go
package eq2net
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"time"
|
|
)
|
|
|
|
// handleIncomingPacket processes incoming network packets
|
|
func (s *EQStream) handleIncomingPacket(data []byte) {
|
|
// Update statistics
|
|
s.stats.PacketsReceived.Add(1)
|
|
s.stats.BytesReceived.Add(uint64(len(data)))
|
|
|
|
// Validate minimum size
|
|
if len(data) < 2 {
|
|
return
|
|
}
|
|
|
|
// Extract opcode
|
|
opcode := binary.BigEndian.Uint16(data[0:2])
|
|
|
|
// Check CRC for non-exempt packets
|
|
if !s.isCRCExempt(opcode) {
|
|
if !ValidateCRC(data, s.config.CRCKey) {
|
|
s.stats.PacketsDropped.Add(1)
|
|
return
|
|
}
|
|
// Remove CRC bytes for further processing
|
|
if len(data) > 2 {
|
|
data = data[:len(data)-2]
|
|
}
|
|
}
|
|
|
|
// Create protocol packet
|
|
packet, err := NewEQProtocolPacketFromBuffer(data, -1)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Handle based on opcode
|
|
switch packet.Opcode {
|
|
case OPSessionRequest:
|
|
s.handleSessionRequest(packet)
|
|
|
|
case OPSessionResponse:
|
|
s.handleSessionResponse(packet)
|
|
|
|
case OPSessionDisconnect:
|
|
s.handleDisconnect(packet)
|
|
|
|
case OPKeepAlive:
|
|
s.handleKeepAlive(packet)
|
|
|
|
case OPAck:
|
|
s.handleAck(packet)
|
|
|
|
case OPOutOfOrderAck:
|
|
s.handleOutOfOrderAck(packet)
|
|
|
|
case OPPacket:
|
|
s.handleDataPacket(packet)
|
|
|
|
case OPFragment:
|
|
s.handleFragment(packet)
|
|
|
|
case OPCombined:
|
|
s.handleCombined(packet)
|
|
|
|
case OPOutOfSession:
|
|
s.handleOutOfSession(packet)
|
|
|
|
default:
|
|
// Unknown opcode, ignore
|
|
}
|
|
}
|
|
|
|
// handleSessionRequest processes incoming session requests
|
|
func (s *EQStream) handleSessionRequest(packet *EQProtocolPacket) {
|
|
if s.GetState() != StreamStateDisconnected {
|
|
// We're not accepting new connections
|
|
s.sendSessionResponse(false)
|
|
return
|
|
}
|
|
|
|
// Parse request
|
|
if len(packet.Buffer) < 10 {
|
|
return
|
|
}
|
|
|
|
version := binary.BigEndian.Uint32(packet.Buffer[0:4])
|
|
sessionID := binary.BigEndian.Uint32(packet.Buffer[4:8])
|
|
maxLength := binary.BigEndian.Uint16(packet.Buffer[8:10])
|
|
|
|
// Validate version
|
|
if version != 2 {
|
|
s.sendSessionResponse(false)
|
|
return
|
|
}
|
|
|
|
// Update session info
|
|
s.sessionID = sessionID
|
|
if int(maxLength) < s.config.MaxPacketSize {
|
|
s.config.MaxPacketSize = int(maxLength)
|
|
}
|
|
|
|
// Accept connection
|
|
s.state.Store(int32(StreamStateConnected))
|
|
s.sendSessionResponse(true)
|
|
|
|
if s.onConnect != nil {
|
|
s.onConnect()
|
|
}
|
|
}
|
|
|
|
// handleSessionResponse processes session response packets
|
|
func (s *EQStream) handleSessionResponse(packet *EQProtocolPacket) {
|
|
if s.GetState() != StreamStateConnecting {
|
|
return
|
|
}
|
|
|
|
// Parse response
|
|
if len(packet.Buffer) < 11 {
|
|
return
|
|
}
|
|
|
|
sessionID := binary.BigEndian.Uint32(packet.Buffer[0:4])
|
|
crcKey := binary.BigEndian.Uint32(packet.Buffer[4:8])
|
|
validation := packet.Buffer[8]
|
|
format := packet.Buffer[9]
|
|
unknownByte := packet.Buffer[10]
|
|
|
|
// Check if accepted
|
|
if validation == 0 {
|
|
// Connection rejected
|
|
s.state.Store(int32(StreamStateDisconnected))
|
|
if s.onDisconnect != nil {
|
|
s.onDisconnect("connection rejected")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Update session info
|
|
s.sessionID = sessionID
|
|
s.config.CRCKey = crcKey
|
|
_ = format // Store for later use if needed
|
|
_ = unknownByte
|
|
|
|
// Connection established
|
|
s.state.Store(int32(StreamStateConnected))
|
|
}
|
|
|
|
// handleDisconnect processes disconnect packets
|
|
func (s *EQStream) handleDisconnect(packet *EQProtocolPacket) {
|
|
s.state.Store(int32(StreamStateDisconnecting))
|
|
|
|
// Send acknowledgment
|
|
ack := NewEQProtocolPacket(OPSessionDisconnect, nil)
|
|
s.sendPacketNow(ack)
|
|
|
|
// Clean shutdown
|
|
if s.onDisconnect != nil {
|
|
s.onDisconnect("remote disconnect")
|
|
}
|
|
|
|
s.Close()
|
|
}
|
|
|
|
// handleKeepAlive processes keep-alive packets
|
|
func (s *EQStream) handleKeepAlive(packet *EQProtocolPacket) {
|
|
// Keep-alives don't require a response, just update last activity time
|
|
// This helps detect dead connections
|
|
}
|
|
|
|
// handleAck processes acknowledgment packets
|
|
func (s *EQStream) handleAck(packet *EQProtocolPacket) {
|
|
if len(packet.Buffer) < 2 {
|
|
return
|
|
}
|
|
|
|
ackSeq := binary.BigEndian.Uint16(packet.Buffer[0:2])
|
|
s.processAck(uint32(ackSeq))
|
|
}
|
|
|
|
// handleOutOfOrderAck processes out-of-order acknowledgments
|
|
func (s *EQStream) handleOutOfOrderAck(packet *EQProtocolPacket) {
|
|
if len(packet.Buffer) < 2 {
|
|
return
|
|
}
|
|
|
|
ackSeq := binary.BigEndian.Uint16(packet.Buffer[0:2])
|
|
s.processAck(uint32(ackSeq))
|
|
}
|
|
|
|
// processAck handles acknowledgment of a sent packet
|
|
func (s *EQStream) processAck(seq uint32) {
|
|
s.sendWindowMu.Lock()
|
|
defer s.sendWindowMu.Unlock()
|
|
|
|
if sp, exists := s.sendWindow[seq]; exists {
|
|
// Calculate RTT and update estimates
|
|
rtt := time.Since(sp.sentTime).Microseconds()
|
|
s.updateRTT(rtt)
|
|
|
|
// Remove from send window
|
|
delete(s.sendWindow, seq)
|
|
|
|
// Update last acknowledged sequence
|
|
if seq > s.lastAckSeq.Load() {
|
|
s.lastAckSeq.Store(seq)
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateRTT updates the RTT estimates using Jacobson/Karels algorithm
|
|
func (s *EQStream) updateRTT(sampleRTT int64) {
|
|
// First sample
|
|
if s.rtt.Load() == 0 {
|
|
s.rtt.Store(sampleRTT)
|
|
s.rttVar.Store(sampleRTT / 2)
|
|
s.rto.Store(sampleRTT + 4*s.rttVar.Load())
|
|
return
|
|
}
|
|
|
|
// Subsequent samples (RFC 6298)
|
|
alpha := int64(125) // 1/8 in fixed point (multiply by 1000)
|
|
beta := int64(250) // 1/4 in fixed point
|
|
|
|
// SRTT = (1-alpha) * SRTT + alpha * RTT
|
|
srtt := s.rtt.Load()
|
|
srtt = ((1000-alpha)*srtt + alpha*sampleRTT) / 1000
|
|
|
|
// RTTVAR = (1-beta) * RTTVAR + beta * |SRTT - RTT|
|
|
var diff int64
|
|
if srtt > sampleRTT {
|
|
diff = srtt - sampleRTT
|
|
} else {
|
|
diff = sampleRTT - srtt
|
|
}
|
|
|
|
rttVar := s.rttVar.Load()
|
|
rttVar = ((1000-beta)*rttVar + beta*diff) / 1000
|
|
|
|
// RTO = SRTT + 4 * RTTVAR
|
|
rto := srtt + 4*rttVar
|
|
|
|
// Minimum RTO of 200ms, maximum of 60s
|
|
if rto < 200000 {
|
|
rto = 200000
|
|
} else if rto > 60000000 {
|
|
rto = 60000000
|
|
}
|
|
|
|
s.rtt.Store(srtt)
|
|
s.rttVar.Store(rttVar)
|
|
s.rto.Store(rto)
|
|
s.stats.RTT.Store(srtt)
|
|
}
|
|
|
|
// handleDataPacket processes regular data packets
|
|
func (s *EQStream) handleDataPacket(packet *EQProtocolPacket) {
|
|
// Extract sequence number (first 2 bytes after opcode)
|
|
if len(packet.Buffer) < 2 {
|
|
return
|
|
}
|
|
|
|
seq := uint32(binary.BigEndian.Uint16(packet.Buffer[0:2]))
|
|
|
|
// Send acknowledgment
|
|
s.sendAck(seq)
|
|
|
|
// Check if it's in order
|
|
expectedSeq := s.nextSeqIn.Load()
|
|
if seq == expectedSeq {
|
|
// In order, process immediately
|
|
s.nextSeqIn.Add(1)
|
|
s.processDataPacket(packet.Buffer[2:])
|
|
|
|
// Check if we have any queued packets that can now be processed
|
|
s.processQueuedPackets()
|
|
} else if seq > expectedSeq {
|
|
// Future packet, queue it
|
|
s.recvWindowMu.Lock()
|
|
s.recvWindow[seq] = packet
|
|
s.recvWindowMu.Unlock()
|
|
|
|
// Send out-of-order ACK
|
|
s.sendOutOfOrderAck(seq)
|
|
}
|
|
// If seq < expectedSeq, it's a duplicate, ignore (we already sent ACK)
|
|
}
|
|
|
|
// processQueuedPackets processes any queued packets that are now in order
|
|
func (s *EQStream) processQueuedPackets() {
|
|
for {
|
|
expectedSeq := s.nextSeqIn.Load()
|
|
|
|
s.recvWindowMu.Lock()
|
|
packet, exists := s.recvWindow[expectedSeq]
|
|
if !exists {
|
|
s.recvWindowMu.Unlock()
|
|
break
|
|
}
|
|
delete(s.recvWindow, expectedSeq)
|
|
s.recvWindowMu.Unlock()
|
|
|
|
s.nextSeqIn.Add(1)
|
|
if len(packet.Buffer) > 2 {
|
|
s.processDataPacket(packet.Buffer[2:])
|
|
}
|
|
}
|
|
}
|
|
|
|
// processDataPacket processes the data portion of a packet
|
|
func (s *EQStream) processDataPacket(data []byte) {
|
|
// Decompress if needed
|
|
if IsCompressed(data) {
|
|
decompressed, err := DecompressPacket(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
data = decompressed
|
|
}
|
|
|
|
// Decrypt chat if needed (check for chat opcodes)
|
|
// This would need opcode inspection
|
|
|
|
// Convert to application packet
|
|
if len(data) < 2 {
|
|
return
|
|
}
|
|
|
|
app := &EQApplicationPacket{
|
|
EQPacket: NewEQPacket(binary.BigEndian.Uint16(data[0:2]), nil),
|
|
}
|
|
|
|
if len(data) > 2 {
|
|
app.Buffer = make([]byte, len(data)-2)
|
|
copy(app.Buffer, data[2:])
|
|
app.Size = uint32(len(app.Buffer))
|
|
}
|
|
|
|
// Queue for application
|
|
select {
|
|
case s.recvQueue <- app:
|
|
default:
|
|
// Receive queue full, drop packet
|
|
s.stats.PacketsDropped.Add(1)
|
|
}
|
|
}
|
|
|
|
// handleFragment processes fragmented packets
|
|
func (s *EQStream) handleFragment(packet *EQProtocolPacket) {
|
|
if len(packet.Buffer) < 6 {
|
|
return
|
|
}
|
|
|
|
// Parse fragment header
|
|
seq := uint32(binary.BigEndian.Uint16(packet.Buffer[0:2]))
|
|
totalSize := binary.BigEndian.Uint32(packet.Buffer[2:6])
|
|
|
|
// Send acknowledgment
|
|
s.sendAck(seq)
|
|
|
|
// Store fragment
|
|
s.recvWindowMu.Lock()
|
|
s.fragmentQueue[seq] = append(s.fragmentQueue[seq], packet)
|
|
|
|
// Check if we have all fragments
|
|
currentSize := uint32(0)
|
|
for _, frag := range s.fragmentQueue[seq] {
|
|
if len(frag.Buffer) > 6 {
|
|
currentSize += uint32(len(frag.Buffer) - 6)
|
|
}
|
|
}
|
|
|
|
if currentSize >= totalSize {
|
|
// Reassemble packet
|
|
reassembled := make([]byte, 0, totalSize)
|
|
for _, frag := range s.fragmentQueue[seq] {
|
|
if len(frag.Buffer) > 6 {
|
|
reassembled = append(reassembled, frag.Buffer[6:]...)
|
|
}
|
|
}
|
|
|
|
// Clean up fragment queue
|
|
delete(s.fragmentQueue, seq)
|
|
s.recvWindowMu.Unlock()
|
|
|
|
// Process reassembled packet
|
|
s.processDataPacket(reassembled)
|
|
} else {
|
|
s.recvWindowMu.Unlock()
|
|
}
|
|
}
|
|
|
|
// handleCombined processes combined packets
|
|
func (s *EQStream) handleCombined(packet *EQProtocolPacket) {
|
|
data := packet.Buffer
|
|
offset := 0
|
|
|
|
for offset < len(data) {
|
|
if offset+1 > len(data) {
|
|
break
|
|
}
|
|
|
|
// Get sub-packet size
|
|
size := int(data[offset])
|
|
offset++
|
|
|
|
// Handle oversized packets (size == 255)
|
|
if size == 255 && offset+2 <= len(data) {
|
|
size = int(binary.BigEndian.Uint16(data[offset:offset+2]))
|
|
offset += 2
|
|
}
|
|
|
|
if offset+size > len(data) {
|
|
break
|
|
}
|
|
|
|
// Process sub-packet
|
|
subData := data[offset : offset+size]
|
|
s.handleIncomingPacket(subData)
|
|
|
|
offset += size
|
|
}
|
|
}
|
|
|
|
// handleOutOfSession processes out-of-session packets
|
|
func (s *EQStream) handleOutOfSession(packet *EQProtocolPacket) {
|
|
// Server is telling us we're not in a session
|
|
s.state.Store(int32(StreamStateDisconnected))
|
|
|
|
if s.onDisconnect != nil {
|
|
s.onDisconnect("out of session")
|
|
}
|
|
}
|
|
|
|
// sendAck sends an acknowledgment packet
|
|
func (s *EQStream) sendAck(seq uint32) {
|
|
data := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data, uint16(seq))
|
|
|
|
ack := NewEQProtocolPacket(OPAck, data)
|
|
s.sendPacketNow(ack)
|
|
}
|
|
|
|
// sendOutOfOrderAck sends an out-of-order acknowledgment
|
|
func (s *EQStream) sendOutOfOrderAck(seq uint32) {
|
|
data := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data, uint16(seq))
|
|
|
|
ack := NewEQProtocolPacket(OPOutOfOrderAck, data)
|
|
s.sendPacketNow(ack)
|
|
}
|
|
|
|
// sendSessionResponse sends a session response packet
|
|
func (s *EQStream) sendSessionResponse(accept bool) {
|
|
data := make([]byte, 11)
|
|
binary.BigEndian.PutUint32(data[0:4], s.sessionID)
|
|
binary.BigEndian.PutUint32(data[4:8], s.config.CRCKey)
|
|
|
|
if accept {
|
|
data[8] = 1 // Validation byte
|
|
} else {
|
|
data[8] = 0 // Rejection
|
|
}
|
|
|
|
data[9] = 0 // Format
|
|
data[10] = 0 // Unknown
|
|
|
|
response := NewEQProtocolPacket(OPSessionResponse, data)
|
|
s.sendPacketNow(response)
|
|
}
|
|
|
|
// FragmentPacket breaks a large packet into fragments
|
|
func (s *EQStream) FragmentPacket(data []byte, maxSize int) []*EQProtocolPacket {
|
|
if len(data) <= maxSize {
|
|
// No fragmentation needed
|
|
return []*EQProtocolPacket{NewEQProtocolPacket(OPPacket, data)}
|
|
}
|
|
|
|
// Calculate fragment sizes
|
|
headerSize := 6 // seq(2) + total_size(4)
|
|
fragmentDataSize := maxSize - headerSize
|
|
numFragments := (len(data) + fragmentDataSize - 1) / fragmentDataSize
|
|
|
|
fragments := make([]*EQProtocolPacket, 0, numFragments)
|
|
totalSize := uint32(len(data))
|
|
|
|
for offset := 0; offset < len(data); offset += fragmentDataSize {
|
|
end := offset + fragmentDataSize
|
|
if end > len(data) {
|
|
end = len(data)
|
|
}
|
|
|
|
// Build fragment packet
|
|
fragData := make([]byte, headerSize+end-offset)
|
|
// Sequence will be set by send worker
|
|
binary.BigEndian.PutUint32(fragData[2:6], totalSize)
|
|
copy(fragData[6:], data[offset:end])
|
|
|
|
fragments = append(fragments, NewEQProtocolPacket(OPFragment, fragData))
|
|
}
|
|
|
|
return fragments
|
|
} |