From 5970198c5afa23455c19c6fc3bd23324ccc08422 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 2 Jul 2025 14:40:24 -0500 Subject: [PATCH] add reliability and security managers to packet handler --- internal/packets/reliability.go | 298 ++++++++++++++++++++++++++++++++ internal/packets/security.go | 243 ++++++++++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 internal/packets/reliability.go create mode 100644 internal/packets/security.go diff --git a/internal/packets/reliability.go b/internal/packets/reliability.go new file mode 100644 index 0000000..a29ce6e --- /dev/null +++ b/internal/packets/reliability.go @@ -0,0 +1,298 @@ +package packets + +import ( + "sync" + "time" + + "eq2emu/internal/opcodes" +) + +type ConnectionState uint8 + +const ( + StateConnecting ConnectionState = iota + StateEstablished + StateClosing + StateClosed +) + +type ReliablePacket struct { + Sequence uint16 + Data []byte + SentTime time.Time + Attempts int + Acked bool +} + +type ReliabilityManager struct { + sessionID uint32 + state ConnectionState + nextSeq uint16 + lastAcked uint16 + + // Outbound reliability + resendQueue map[uint16]*ReliablePacket + resendTimer *time.Timer + + // Inbound reliability + expectedSeq uint16 + outOfOrder map[uint16][]byte + + // Configuration + maxRetries int + retryDelay time.Duration + ackDelay time.Duration + + mu sync.RWMutex + onSend func([]byte) + onReceive func([]byte) +} + +func NewReliabilityManager(sessionID uint32) *ReliabilityManager { + rm := &ReliabilityManager{ + sessionID: sessionID, + state: StateConnecting, + resendQueue: make(map[uint16]*ReliablePacket), + outOfOrder: make(map[uint16][]byte), + maxRetries: 5, + retryDelay: time.Millisecond * 500, + ackDelay: time.Millisecond * 200, + } + + rm.startResendTimer() + return rm +} + +func (rm *ReliabilityManager) SetCallbacks(onSend, onReceive func([]byte)) { + rm.mu.Lock() + defer rm.mu.Unlock() + rm.onSend = onSend + rm.onReceive = onReceive +} + +func (rm *ReliabilityManager) SetState(state ConnectionState) { + rm.mu.Lock() + defer rm.mu.Unlock() + rm.state = state +} + +func (rm *ReliabilityManager) GetState() ConnectionState { + rm.mu.RLock() + defer rm.mu.RUnlock() + return rm.state +} + +// Send packet with reliability +func (rm *ReliabilityManager) SendReliable(data []byte) uint16 { + rm.mu.Lock() + defer rm.mu.Unlock() + + seq := rm.nextSeq + rm.nextSeq++ + + packet := &ReliablePacket{ + Sequence: seq, + Data: make([]byte, len(data)), + SentTime: time.Now(), + Attempts: 1, + } + copy(packet.Data, data) + + rm.resendQueue[seq] = packet + + if rm.onSend != nil { + rm.onSend(data) + } + + return seq +} + +// Send unreliable packet +func (rm *ReliabilityManager) SendUnreliable(data []byte) { + rm.mu.RLock() + onSend := rm.onSend + rm.mu.RUnlock() + + if onSend != nil { + onSend(data) + } +} + +// Process incoming packet +func (rm *ReliabilityManager) ProcessIncoming(data []byte) { + if len(data) < 1 { + return + } + + opcode := opcodes.ProtocolOpcode(data[0]) + + switch opcode { + case opcodes.OP_Ack: + rm.handleAck(data) + case opcodes.OP_Packet: + rm.handleSequencedPacket(data) + case opcodes.OP_Combined: + rm.handleSequencedPacket(data) + default: + // Unreliable packet + rm.deliverPacket(data) + } +} + +func (rm *ReliabilityManager) handleAck(data []byte) { + if len(data) < 3 { + return + } + + seq := uint16(data[1]) | (uint16(data[2]) << 8) + + rm.mu.Lock() + defer rm.mu.Unlock() + + if packet, exists := rm.resendQueue[seq]; exists { + packet.Acked = true + delete(rm.resendQueue, seq) + + if seq > rm.lastAcked { + rm.lastAcked = seq + } + } +} + +func (rm *ReliabilityManager) handleSequencedPacket(data []byte) { + if len(data) < 4 { + return + } + + // Extract sequence (bytes 1-2) + seq := uint16(data[1]) | (uint16(data[2]) << 8) + + rm.mu.Lock() + defer rm.mu.Unlock() + + // Send ACK + rm.sendAck(seq) + + if seq == rm.expectedSeq { + // In order - deliver immediately + rm.deliverPacketLocked(data) + rm.expectedSeq++ + + // Check for buffered packets + rm.processBufferedPackets() + } else if seq > rm.expectedSeq { + // Out of order - buffer it + buffered := make([]byte, len(data)) + copy(buffered, data) + rm.outOfOrder[seq] = buffered + } + // Ignore old/duplicate packets (seq < expectedSeq) +} + +func (rm *ReliabilityManager) processBufferedPackets() { + for rm.outOfOrder[rm.expectedSeq] != nil { + data := rm.outOfOrder[rm.expectedSeq] + delete(rm.outOfOrder, rm.expectedSeq) + + rm.deliverPacketLocked(data) + rm.expectedSeq++ + } +} + +func (rm *ReliabilityManager) sendAck(seq uint16) { + ack := []byte{ + byte(opcodes.OP_Ack), + byte(seq & 0xFF), + byte((seq >> 8) & 0xFF), + } + + if rm.onSend != nil { + go rm.onSend(ack) + } +} + +func (rm *ReliabilityManager) deliverPacket(data []byte) { + rm.mu.RLock() + onReceive := rm.onReceive + rm.mu.RUnlock() + + if onReceive != nil { + onReceive(data) + } +} + +func (rm *ReliabilityManager) deliverPacketLocked(data []byte) { + if rm.onReceive != nil { + // Make copy since we're calling from locked context + delivered := make([]byte, len(data)) + copy(delivered, data) + go rm.onReceive(delivered) + } +} + +func (rm *ReliabilityManager) startResendTimer() { + rm.resendTimer = time.AfterFunc(rm.retryDelay, func() { + rm.checkResends() + rm.startResendTimer() + }) +} + +func (rm *ReliabilityManager) checkResends() { + rm.mu.Lock() + defer rm.mu.Unlock() + + now := time.Now() + var toResend []*ReliablePacket + + for seq, packet := range rm.resendQueue { + if packet.Acked { + continue + } + + if now.Sub(packet.SentTime) > rm.retryDelay { + if packet.Attempts >= rm.maxRetries { + // Give up on this packet + delete(rm.resendQueue, seq) + continue + } + + packet.Attempts++ + packet.SentTime = now + toResend = append(toResend, packet) + } + } + + // Send outside of lock + if len(toResend) > 0 && rm.onSend != nil { + go func() { + for _, packet := range toResend { + rm.onSend(packet.Data) + } + }() + } +} + +func (rm *ReliabilityManager) Close() { + rm.mu.Lock() + defer rm.mu.Unlock() + + if rm.resendTimer != nil { + rm.resendTimer.Stop() + } + + rm.state = StateClosed + rm.resendQueue = make(map[uint16]*ReliablePacket) + rm.outOfOrder = make(map[uint16][]byte) +} + +// Get stats for monitoring +func (rm *ReliabilityManager) GetStats() (int, int, int) { + rm.mu.RLock() + defer rm.mu.RUnlock() + + pending := len(rm.resendQueue) + buffered := len(rm.outOfOrder) + nextSeq := int(rm.nextSeq) + + return pending, buffered, nextSeq +} diff --git a/internal/packets/security.go b/internal/packets/security.go new file mode 100644 index 0000000..b402da0 --- /dev/null +++ b/internal/packets/security.go @@ -0,0 +1,243 @@ +package packets + +import ( + "crypto/rand" + "crypto/rc4" + "crypto/rsa" + "crypto/x509" + "encoding/binary" + "fmt" + + "eq2emu/internal/crc16" + "eq2emu/internal/opcodes" +) + +type SecurityManager struct { + sessionKey uint32 + rc4Cipher *rc4.Cipher + rsaKey *rsa.PrivateKey + encrypted bool + compressed bool +} + +func NewSecurityManager() *SecurityManager { + // Generate RSA key for session establishment + key, _ := rsa.GenerateKey(rand.Reader, 1024) + + return &SecurityManager{ + rsaKey: key, + encrypted: false, + } +} + +// Validate packet CRC +func (sm *SecurityManager) ValidateCRC(data []byte, key uint32) bool { + if len(data) < 2 { + return false + } + + // Skip CRC validation for session packets + if len(data) >= 2 { + opcode := opcodes.ProtocolOpcode(data[0]) + if opcode == opcodes.OP_SessionRequest || + opcode == opcodes.OP_SessionResponse || + opcode == opcodes.OP_OutOfSession { + return true + } + } + + // Extract CRC from last 2 bytes + packetCRC := binary.LittleEndian.Uint16(data[len(data)-2:]) + if packetCRC == 0 { + return true // No CRC + } + + // Calculate CRC on data without the CRC bytes + calcCRC := uint16(crc16.Calculate(data[:len(data)-2], key)) + return calcCRC == packetCRC +} + +// Append CRC to packet +func (sm *SecurityManager) AppendCRC(data []byte, key uint32) []byte { + // Skip CRC for session packets + if len(data) >= 1 { + opcode := opcodes.ProtocolOpcode(data[0]) + if opcode == opcodes.OP_SessionRequest || + opcode == opcodes.OP_SessionResponse || + opcode == opcodes.OP_OutOfSession { + return data + } + } + + crc := uint16(crc16.Calculate(data, key)) + result := make([]byte, len(data)+2) + copy(result, data) + binary.LittleEndian.PutUint16(result[len(data):], crc) + return result +} + +// Chat encoding (custom XOR encryption) +func (sm *SecurityManager) ChatEncode(data []byte, key uint32) { + if len(data) < 2 { + return + } + + // Skip encoding for certain opcodes + if data[1] == 0x01 || data[0] == 0x02 || data[0] == 0x1d { + return + } + + // Skip opcode bytes + payload := data[2:] + encKey := key + + // Encode 4-byte chunks + for i := 0; i+3 < len(payload); i += 4 { + chunk := binary.LittleEndian.Uint32(payload[i:]) + encoded := chunk ^ encKey + encKey = encoded + binary.LittleEndian.PutUint32(payload[i:], encoded) + } + + // Encode remaining bytes + keyByte := uint8(encKey & 0xFF) + for i := (len(payload) / 4) * 4; i < len(payload); i++ { + payload[i] ^= keyByte + } +} + +// Chat decoding +func (sm *SecurityManager) ChatDecode(data []byte, key uint32) { + if len(data) < 2 { + return + } + + // Skip decoding for certain opcodes + if data[1] == 0x01 || data[0] == 0x02 || data[0] == 0x1d { + return + } + + // Skip opcode bytes + payload := data[2:] + decKey := key + + // Decode 4-byte chunks + for i := 0; i+3 < len(payload); i += 4 { + chunk := binary.LittleEndian.Uint32(payload[i:]) + decoded := chunk ^ decKey + decKey = chunk + binary.LittleEndian.PutUint32(payload[i:], decoded) + } + + // Decode remaining bytes + keyByte := uint8(decKey & 0xFF) + for i := (len(payload) / 4) * 4; i < len(payload); i++ { + payload[i] ^= keyByte + } +} + +// RC4 encryption setup +func (sm *SecurityManager) SetRC4Key(key []byte) error { + cipher, err := rc4.NewCipher(key) + if err != nil { + return err + } + sm.rc4Cipher = cipher + sm.encrypted = true + return nil +} + +// RC4 encrypt/decrypt +func (sm *SecurityManager) RC4Crypt(data []byte) { + if sm.rc4Cipher != nil { + sm.rc4Cipher.XORKeyStream(data, data) + } +} + +// RSA decrypt (for key exchange) +func (sm *SecurityManager) RSADecrypt(data []byte) ([]byte, error) { + if sm.rsaKey == nil { + return nil, fmt.Errorf("no RSA key") + } + + return rsa.DecryptPKCS1v15(rand.Reader, sm.rsaKey, data) +} + +// Get RSA public key for client +func (sm *SecurityManager) GetRSAPublicKey() ([]byte, error) { + if sm.rsaKey == nil { + return nil, fmt.Errorf("no RSA key") + } + + return x509.MarshalPKIXPublicKey(&sm.rsaKey.PublicKey) +} + +// Process encrypted packet +func (sm *SecurityManager) ProcessEncryptedPacket(data []byte) []byte { + if len(data) < 3 { + return data + } + + // Make copy to avoid modifying original + result := make([]byte, len(data)) + copy(result, data) + + // Apply RC4 if enabled + if sm.encrypted && sm.rc4Cipher != nil { + sm.RC4Crypt(result) + } + + // Apply chat decoding + if sm.sessionKey != 0 { + sm.ChatDecode(result, sm.sessionKey) + } + + return result +} + +// Prepare packet for sending +func (sm *SecurityManager) PrepareOutgoingPacket(data []byte) []byte { + // Make copy + result := make([]byte, len(data)) + copy(result, data) + + // Apply chat encoding + if sm.sessionKey != 0 { + sm.ChatEncode(result, sm.sessionKey) + } + + // Apply RC4 if enabled + if sm.encrypted && sm.rc4Cipher != nil { + sm.RC4Crypt(result) + } + + // Append CRC + result = sm.AppendCRC(result, sm.sessionKey) + + return result +} + +// Session management +func (sm *SecurityManager) SetSessionKey(key uint32) { + sm.sessionKey = key +} + +func (sm *SecurityManager) GetSessionKey() uint32 { + return sm.sessionKey +} + +func (sm *SecurityManager) SetEncrypted(encrypted bool) { + sm.encrypted = encrypted +} + +func (sm *SecurityManager) IsEncrypted() bool { + return sm.encrypted +} + +func (sm *SecurityManager) SetCompressed(compressed bool) { + sm.compressed = compressed +} + +func (sm *SecurityManager) IsCompressed() bool { + return sm.compressed +}