add reliability and security managers to packet handler
This commit is contained in:
parent
9af0a372aa
commit
5970198c5a
298
internal/packets/reliability.go
Normal file
298
internal/packets/reliability.go
Normal file
@ -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
|
||||||
|
}
|
243
internal/packets/security.go
Normal file
243
internal/packets/security.go
Normal file
@ -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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user