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