1
0
Protocol/stream_packet_handler.go
2025-09-01 13:56:01 -05:00

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
}