packets
This commit is contained in:
parent
aac862aebe
commit
fd7126878f
209
compression.go
Normal file
209
compression.go
Normal file
@ -0,0 +1,209 @@
|
||||
package eq2net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
// Compression flags
|
||||
CompressionFlagZlib = 0x5A // Zlib compression
|
||||
CompressionFlagSimple = 0xA5 // Simple encoding (no actual compression)
|
||||
|
||||
// Compression threshold - packets larger than this use zlib
|
||||
CompressionThreshold = 30
|
||||
)
|
||||
|
||||
// CompressPacket compresses a packet using zlib or simple encoding
|
||||
func CompressPacket(data []byte) ([]byte, error) {
|
||||
if len(data) < 2 {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Determine opcode size
|
||||
flagOffset := 1
|
||||
if data[0] == 0 {
|
||||
flagOffset = 2 // Two-byte opcode
|
||||
}
|
||||
|
||||
// Don't compress if too small
|
||||
if len(data) <= flagOffset {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
result := make([]byte, 0, len(data)+1)
|
||||
|
||||
// Copy opcode bytes
|
||||
result = append(result, data[:flagOffset]...)
|
||||
|
||||
if len(data) > CompressionThreshold {
|
||||
// Use zlib compression for larger packets
|
||||
result = append(result, CompressionFlagZlib)
|
||||
|
||||
// Compress the data after opcode
|
||||
var compressed bytes.Buffer
|
||||
w := zlib.NewWriter(&compressed)
|
||||
if _, err := w.Write(data[flagOffset:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, compressed.Bytes()...)
|
||||
} else {
|
||||
// Use simple encoding for smaller packets
|
||||
result = append(result, CompressionFlagSimple)
|
||||
result = append(result, data[flagOffset:]...)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DecompressPacket decompresses a packet
|
||||
func DecompressPacket(data []byte) ([]byte, error) {
|
||||
if len(data) < 3 {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Determine opcode size and compression flag position
|
||||
flagOffset := 1
|
||||
if data[0] == 0 {
|
||||
flagOffset = 2
|
||||
}
|
||||
|
||||
if len(data) <= flagOffset {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
compressionFlag := data[flagOffset]
|
||||
|
||||
// Check compression type
|
||||
switch compressionFlag {
|
||||
case CompressionFlagZlib:
|
||||
// Zlib decompression
|
||||
result := make([]byte, 0, len(data)*2)
|
||||
|
||||
// Copy opcode
|
||||
result = append(result, data[:flagOffset]...)
|
||||
|
||||
// Decompress data (skip flag byte)
|
||||
r, err := zlib.NewReader(bytes.NewReader(data[flagOffset+1:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
decompressed, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, decompressed...)
|
||||
return result, nil
|
||||
|
||||
case CompressionFlagSimple:
|
||||
// Simple encoding - just remove the flag byte
|
||||
result := make([]byte, 0, len(data)-1)
|
||||
result = append(result, data[:flagOffset]...)
|
||||
result = append(result, data[flagOffset+1:]...)
|
||||
return result, nil
|
||||
|
||||
default:
|
||||
// No compression
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsCompressed checks if a packet is compressed
|
||||
func IsCompressed(data []byte) bool {
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
flagOffset := 1
|
||||
if data[0] == 0 {
|
||||
flagOffset = 2
|
||||
}
|
||||
|
||||
if len(data) <= flagOffset {
|
||||
return false
|
||||
}
|
||||
|
||||
flag := data[flagOffset]
|
||||
return flag == CompressionFlagZlib || flag == CompressionFlagSimple
|
||||
}
|
||||
|
||||
// ChatEncode encodes chat data using XOR encryption with rolling key
|
||||
func ChatEncode(data []byte, encodeKey uint32) []byte {
|
||||
// Skip certain packet types
|
||||
if len(data) >= 2 && (data[1] == 0x01 || data[0] == 0x02 || data[0] == 0x1d) {
|
||||
return data
|
||||
}
|
||||
|
||||
// Work with data after opcode
|
||||
if len(data) <= 2 {
|
||||
return data
|
||||
}
|
||||
|
||||
result := make([]byte, len(data))
|
||||
copy(result[:2], data[:2]) // Copy opcode
|
||||
|
||||
key := encodeKey
|
||||
offset := 2
|
||||
|
||||
// Process 4-byte blocks with rolling key
|
||||
for i := offset; i+4 <= len(data); i += 4 {
|
||||
block := binary.LittleEndian.Uint32(data[i : i+4])
|
||||
encrypted := block ^ key
|
||||
binary.LittleEndian.PutUint32(result[i:i+4], encrypted)
|
||||
key = encrypted // Update key with encrypted data
|
||||
}
|
||||
|
||||
// Handle remaining bytes
|
||||
keyByte := byte(key & 0xFF)
|
||||
alignedEnd := offset + ((len(data)-offset)/4)*4
|
||||
for i := alignedEnd; i < len(data); i++ {
|
||||
result[i] = data[i] ^ keyByte
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ChatDecode decodes chat data using XOR encryption with rolling key
|
||||
func ChatDecode(data []byte, decodeKey uint32) []byte {
|
||||
// Skip certain packet types
|
||||
if len(data) >= 2 && (data[1] == 0x01 || data[0] == 0x02 || data[0] == 0x1d) {
|
||||
return data
|
||||
}
|
||||
|
||||
// Work with data after opcode
|
||||
if len(data) <= 2 {
|
||||
return data
|
||||
}
|
||||
|
||||
result := make([]byte, len(data))
|
||||
copy(result[:2], data[:2]) // Copy opcode
|
||||
|
||||
key := decodeKey
|
||||
offset := 2
|
||||
|
||||
// Process 4-byte blocks with rolling key
|
||||
for i := offset; i+4 <= len(data); i += 4 {
|
||||
encrypted := binary.LittleEndian.Uint32(data[i : i+4])
|
||||
decrypted := encrypted ^ key
|
||||
binary.LittleEndian.PutUint32(result[i:i+4], decrypted)
|
||||
key = encrypted // Update key with encrypted data (before decryption)
|
||||
}
|
||||
|
||||
// Handle remaining bytes
|
||||
keyByte := byte(key & 0xFF)
|
||||
alignedEnd := offset + ((len(data)-offset)/4)*4
|
||||
for i := alignedEnd; i < len(data); i++ {
|
||||
result[i] = data[i] ^ keyByte
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
93
crc16.go
Normal file
93
crc16.go
Normal file
@ -0,0 +1,93 @@
|
||||
package eq2net
|
||||
|
||||
// CRC16 table for CCITT polynomial (0x1021)
|
||||
var crc16Table [256]uint16
|
||||
|
||||
func init() {
|
||||
// Initialize CRC16 lookup table
|
||||
for i := 0; i < 256; i++ {
|
||||
crc := uint16(i << 8)
|
||||
for j := 0; j < 8; j++ {
|
||||
if (crc & 0x8000) != 0 {
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
} else {
|
||||
crc = crc << 1
|
||||
}
|
||||
}
|
||||
crc16Table[i] = crc
|
||||
}
|
||||
}
|
||||
|
||||
// CRC16 calculates the CRC16-CCITT checksum with a key
|
||||
func CRC16(data []byte, length int, key uint32) uint16 {
|
||||
if length <= 0 || len(data) < length {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Mix the key into initial CRC value
|
||||
crc := uint16(0xFFFF)
|
||||
keyBytes := []byte{
|
||||
byte(key),
|
||||
byte(key >> 8),
|
||||
byte(key >> 16),
|
||||
byte(key >> 24),
|
||||
}
|
||||
|
||||
// Process key bytes first
|
||||
for _, b := range keyBytes {
|
||||
tableIndex := (uint8(crc>>8) ^ b) & 0xFF
|
||||
crc = (crc << 8) ^ crc16Table[tableIndex]
|
||||
}
|
||||
|
||||
// Process data
|
||||
for i := 0; i < length; i++ {
|
||||
tableIndex := (uint8(crc>>8) ^ data[i]) & 0xFF
|
||||
crc = (crc << 8) ^ crc16Table[tableIndex]
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFF
|
||||
}
|
||||
|
||||
// ValidateCRC checks if a packet has a valid CRC
|
||||
func ValidateCRC(buffer []byte, key uint32) bool {
|
||||
if len(buffer) < 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for CRC-exempt packets
|
||||
if len(buffer) >= 2 && buffer[0] == 0x00 {
|
||||
switch buffer[1] {
|
||||
case byte(OPSessionRequest), byte(OPSessionResponse), byte(OPOutOfSession):
|
||||
return true // Session packets don't require CRC
|
||||
}
|
||||
}
|
||||
|
||||
// Check for combined application packets (also CRC-exempt)
|
||||
if len(buffer) >= 4 && buffer[2] == 0x00 && buffer[3] == 0x19 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Calculate CRC for the packet (excluding last 2 CRC bytes)
|
||||
dataLen := len(buffer) - 2
|
||||
calculatedCRC := CRC16(buffer, dataLen, key)
|
||||
|
||||
// Extract packet CRC (big-endian in last 2 bytes)
|
||||
packetCRC := uint16(buffer[dataLen])<<8 | uint16(buffer[dataLen+1])
|
||||
|
||||
// Valid if no CRC present (0) or CRCs match
|
||||
return packetCRC == 0 || calculatedCRC == packetCRC
|
||||
}
|
||||
|
||||
// AppendCRC adds CRC to the end of a packet
|
||||
func AppendCRC(buffer []byte, key uint32) []byte {
|
||||
// Calculate CRC for current buffer
|
||||
crc := CRC16(buffer, len(buffer), key)
|
||||
|
||||
// Append CRC in big-endian format
|
||||
result := make([]byte, len(buffer)+2)
|
||||
copy(result, buffer)
|
||||
result[len(buffer)] = byte(crc >> 8)
|
||||
result[len(buffer)+1] = byte(crc)
|
||||
|
||||
return result
|
||||
}
|
299
packet.go
Normal file
299
packet.go
Normal file
@ -0,0 +1,299 @@
|
||||
// Package eq2net implements the EverQuest 2 network protocol
|
||||
package eq2net
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Protocol opcodes for low-level packet control
|
||||
const (
|
||||
OPSessionRequest = 0x0001
|
||||
OPSessionResponse = 0x0002
|
||||
OPCombined = 0x0003
|
||||
OPSessionDisconnect = 0x0005
|
||||
OPKeepAlive = 0x0006
|
||||
OPSessionStatRequest = 0x0007
|
||||
OPSessionStatResponse = 0x0008
|
||||
OPPacket = 0x0009
|
||||
OPFragment = 0x000D
|
||||
OPOutOfOrderAck = 0x0011
|
||||
OPAck = 0x0015
|
||||
OPAppCombined = 0x0019
|
||||
OPOutOfSession = 0x001D
|
||||
)
|
||||
|
||||
// EQPacket is the base packet type for all EverQuest packets
|
||||
type EQPacket struct {
|
||||
// Core packet data
|
||||
Buffer []byte
|
||||
Size uint32
|
||||
Opcode uint16
|
||||
|
||||
// Network information
|
||||
SrcIP net.IP
|
||||
DstIP net.IP
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
|
||||
// Metadata
|
||||
Priority uint32
|
||||
Timestamp time.Time
|
||||
Version int16
|
||||
}
|
||||
|
||||
// NewEQPacket creates a new packet with the specified opcode and data
|
||||
func NewEQPacket(opcode uint16, data []byte) *EQPacket {
|
||||
p := &EQPacket{
|
||||
Opcode: opcode,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
p.Buffer = make([]byte, len(data))
|
||||
copy(p.Buffer, data)
|
||||
p.Size = uint32(len(data))
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// TotalSize returns the total packet size including opcode
|
||||
func (p *EQPacket) TotalSize() uint32 {
|
||||
return p.Size + 2 // +2 for opcode
|
||||
}
|
||||
|
||||
// SetNetworkInfo sets the source and destination network information
|
||||
func (p *EQPacket) SetNetworkInfo(srcIP net.IP, srcPort uint16, dstIP net.IP, dstPort uint16) {
|
||||
p.SrcIP = srcIP
|
||||
p.SrcPort = srcPort
|
||||
p.DstIP = dstIP
|
||||
p.DstPort = dstPort
|
||||
}
|
||||
|
||||
// CopyInfo copies network and timing information from another packet
|
||||
func (p *EQPacket) CopyInfo(other *EQPacket) {
|
||||
p.SrcIP = other.SrcIP
|
||||
p.SrcPort = other.SrcPort
|
||||
p.DstIP = other.DstIP
|
||||
p.DstPort = other.DstPort
|
||||
p.Timestamp = other.Timestamp
|
||||
p.Version = other.Version
|
||||
}
|
||||
|
||||
// EQProtocolPacket handles low-level protocol operations
|
||||
type EQProtocolPacket struct {
|
||||
*EQPacket
|
||||
|
||||
// Protocol state flags
|
||||
Compressed bool
|
||||
Prepared bool
|
||||
Encrypted bool
|
||||
Acked bool
|
||||
|
||||
// Reliability tracking
|
||||
SentTime time.Time
|
||||
AttemptCount uint8
|
||||
Sequence uint32
|
||||
}
|
||||
|
||||
// NewEQProtocolPacket creates a new protocol packet
|
||||
func NewEQProtocolPacket(opcode uint16, data []byte) *EQProtocolPacket {
|
||||
return &EQProtocolPacket{
|
||||
EQPacket: NewEQPacket(opcode, data),
|
||||
}
|
||||
}
|
||||
|
||||
// NewEQProtocolPacketFromBuffer creates a protocol packet from raw buffer
|
||||
func NewEQProtocolPacketFromBuffer(buffer []byte, opcodeOverride int) (*EQProtocolPacket, error) {
|
||||
if len(buffer) < 2 {
|
||||
return nil, fmt.Errorf("buffer too small for opcode")
|
||||
}
|
||||
|
||||
var opcode uint16
|
||||
var dataOffset int
|
||||
|
||||
if opcodeOverride >= 0 {
|
||||
opcode = uint16(opcodeOverride)
|
||||
dataOffset = 0
|
||||
} else {
|
||||
opcode = binary.BigEndian.Uint16(buffer[:2])
|
||||
dataOffset = 2
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if len(buffer) > dataOffset {
|
||||
data = buffer[dataOffset:]
|
||||
}
|
||||
|
||||
return NewEQProtocolPacket(opcode, data), nil
|
||||
}
|
||||
|
||||
// Serialize writes the protocol packet to a byte buffer
|
||||
func (p *EQProtocolPacket) Serialize(offset int) []byte {
|
||||
// Allocate buffer for opcode + data
|
||||
result := make([]byte, 2+len(p.Buffer)-offset)
|
||||
|
||||
// Write opcode (big-endian)
|
||||
if p.Opcode > 0xFF {
|
||||
binary.BigEndian.PutUint16(result[0:2], p.Opcode)
|
||||
} else {
|
||||
result[0] = 0
|
||||
result[1] = byte(p.Opcode)
|
||||
}
|
||||
|
||||
// Copy packet data
|
||||
if len(p.Buffer) > offset {
|
||||
copy(result[2:], p.Buffer[offset:])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IsProtocolPacket checks if the opcode is a valid protocol packet
|
||||
func IsProtocolPacket(opcode uint16) bool {
|
||||
switch opcode {
|
||||
case OPSessionRequest, OPSessionDisconnect, OPKeepAlive,
|
||||
OPSessionStatResponse, OPPacket, OPCombined, OPFragment,
|
||||
OPAck, OPOutOfOrderAck, OPOutOfSession:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the protocol packet
|
||||
func (p *EQProtocolPacket) Copy() *EQProtocolPacket {
|
||||
newPacket := &EQProtocolPacket{
|
||||
EQPacket: NewEQPacket(p.Opcode, p.Buffer),
|
||||
Compressed: p.Compressed,
|
||||
Prepared: p.Prepared,
|
||||
Encrypted: p.Encrypted,
|
||||
Acked: p.Acked,
|
||||
SentTime: p.SentTime,
|
||||
AttemptCount: p.AttemptCount,
|
||||
Sequence: p.Sequence,
|
||||
}
|
||||
newPacket.CopyInfo(p.EQPacket)
|
||||
return newPacket
|
||||
}
|
||||
|
||||
// EQApplicationPacket represents high-level application packets
|
||||
type EQApplicationPacket struct {
|
||||
*EQPacket
|
||||
|
||||
// Cached emulator opcode
|
||||
EmuOpcode uint16
|
||||
|
||||
// Opcode size (1 or 2 bytes)
|
||||
OpcodeSize uint8
|
||||
}
|
||||
|
||||
// DefaultOpcodeSize is the default size for application opcodes
|
||||
var DefaultOpcodeSize uint8 = 2
|
||||
|
||||
// NewEQApplicationPacket creates a new application packet
|
||||
func NewEQApplicationPacket(opcode uint16, data []byte) *EQApplicationPacket {
|
||||
return &EQApplicationPacket{
|
||||
EQPacket: NewEQPacket(opcode, data),
|
||||
EmuOpcode: opcode,
|
||||
OpcodeSize: DefaultOpcodeSize,
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize writes the application packet to a byte buffer
|
||||
func (p *EQApplicationPacket) Serialize() []byte {
|
||||
opcodeBytes := p.OpcodeSize
|
||||
|
||||
// Special handling for opcodes with low byte = 0x00
|
||||
if p.OpcodeSize == 2 && (p.Opcode&0x00FF) == 0 {
|
||||
opcodeBytes = 3
|
||||
}
|
||||
|
||||
result := make([]byte, uint32(opcodeBytes)+p.Size)
|
||||
|
||||
if p.OpcodeSize == 1 {
|
||||
result[0] = byte(p.Opcode)
|
||||
} else {
|
||||
if (p.Opcode & 0x00FF) == 0 {
|
||||
result[0] = 0
|
||||
binary.BigEndian.PutUint16(result[1:3], p.Opcode)
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(result[0:2], p.Opcode)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy data after opcode
|
||||
if p.Size > 0 {
|
||||
copy(result[opcodeBytes:], p.Buffer)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the application packet
|
||||
func (p *EQApplicationPacket) Copy() *EQApplicationPacket {
|
||||
newPacket := &EQApplicationPacket{
|
||||
EQPacket: NewEQPacket(p.Opcode, p.Buffer),
|
||||
EmuOpcode: p.EmuOpcode,
|
||||
OpcodeSize: p.OpcodeSize,
|
||||
}
|
||||
newPacket.CopyInfo(p.EQPacket)
|
||||
return newPacket
|
||||
}
|
||||
|
||||
// PacketCombiner handles combining multiple packets for efficient transmission
|
||||
type PacketCombiner struct {
|
||||
maxSize int
|
||||
}
|
||||
|
||||
// NewPacketCombiner creates a new packet combiner with max size limit
|
||||
func NewPacketCombiner(maxSize int) *PacketCombiner {
|
||||
return &PacketCombiner{maxSize: maxSize}
|
||||
}
|
||||
|
||||
// CombineProtocolPackets combines multiple protocol packets into one
|
||||
func (c *PacketCombiner) CombineProtocolPackets(packets []*EQProtocolPacket) *EQProtocolPacket {
|
||||
if len(packets) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(packets) == 1 {
|
||||
return packets[0]
|
||||
}
|
||||
|
||||
// Calculate total size needed
|
||||
totalSize := 0
|
||||
for _, p := range packets {
|
||||
totalSize += 1 + int(p.TotalSize()) // 1 byte for size prefix
|
||||
}
|
||||
|
||||
if totalSize > c.maxSize {
|
||||
return nil // Too large to combine
|
||||
}
|
||||
|
||||
// Build combined packet buffer
|
||||
buffer := make([]byte, totalSize)
|
||||
offset := 0
|
||||
|
||||
for _, p := range packets {
|
||||
// Write size prefix
|
||||
buffer[offset] = byte(p.TotalSize())
|
||||
offset++
|
||||
|
||||
// Serialize packet
|
||||
serialized := p.Serialize(0)
|
||||
copy(buffer[offset:], serialized)
|
||||
offset += len(serialized)
|
||||
}
|
||||
|
||||
// Create combined packet
|
||||
combined := NewEQProtocolPacket(OPCombined, buffer)
|
||||
if len(packets) > 0 {
|
||||
combined.CopyInfo(packets[0].EQPacket)
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
207
packet_test.go
Normal file
207
packet_test.go
Normal file
@ -0,0 +1,207 @@
|
||||
package eq2net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEQPacket(t *testing.T) {
|
||||
data := []byte("Hello, World!")
|
||||
packet := NewEQPacket(OPPacket, data)
|
||||
|
||||
if packet.Opcode != OPPacket {
|
||||
t.Errorf("Expected opcode %04x, got %04x", OPPacket, packet.Opcode)
|
||||
}
|
||||
|
||||
if packet.Size != uint32(len(data)) {
|
||||
t.Errorf("Expected size %d, got %d", len(data), packet.Size)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packet.Buffer, data) {
|
||||
t.Errorf("Buffer mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolPacketSerialization(t *testing.T) {
|
||||
data := []byte("Test Data")
|
||||
packet := NewEQProtocolPacket(OPPacket, data)
|
||||
|
||||
serialized := packet.Serialize(0)
|
||||
|
||||
// Check opcode (first 2 bytes)
|
||||
if len(serialized) < 2 {
|
||||
t.Fatal("Serialized packet too small")
|
||||
}
|
||||
|
||||
// OPPacket = 0x0009, should be [0x00, 0x09] in big-endian
|
||||
if serialized[0] != 0x00 || serialized[1] != 0x09 {
|
||||
t.Errorf("Opcode not serialized correctly: %02x %02x", serialized[0], serialized[1])
|
||||
}
|
||||
|
||||
// Check data
|
||||
if !bytes.Equal(serialized[2:], data) {
|
||||
t.Error("Data not serialized correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCRC16(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
key uint32
|
||||
}{
|
||||
{[]byte{0x00, 0x09, 0x00, 0x00, 0x00}, 0x12345678},
|
||||
{[]byte{0x00, 0x01}, 0},
|
||||
{[]byte("Hello"), 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Just test that CRC16 produces consistent results
|
||||
got1 := CRC16(tt.data, len(tt.data), tt.key)
|
||||
got2 := CRC16(tt.data, len(tt.data), tt.key)
|
||||
if got1 != got2 {
|
||||
t.Errorf("CRC16 not consistent: %04x != %04x", got1, got2)
|
||||
}
|
||||
// Test that different keys produce different CRCs
|
||||
if tt.key != 0 {
|
||||
got3 := CRC16(tt.data, len(tt.data), 0)
|
||||
if got1 == got3 {
|
||||
t.Errorf("Different keys should produce different CRCs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCRC(t *testing.T) {
|
||||
// Test session packet (CRC exempt)
|
||||
sessionPacket := []byte{0x00, byte(OPSessionRequest), 0x00, 0x00}
|
||||
if !ValidateCRC(sessionPacket, 0) {
|
||||
t.Error("Session packet should be CRC exempt")
|
||||
}
|
||||
|
||||
// Test packet with valid CRC
|
||||
data := []byte{0x00, 0x09, 0x48, 0x65, 0x6C, 0x6C, 0x6F} // "Hello"
|
||||
crc := CRC16(data, len(data), 0x1234)
|
||||
dataWithCRC := append(data, byte(crc>>8), byte(crc))
|
||||
|
||||
if !ValidateCRC(dataWithCRC, 0x1234) {
|
||||
t.Error("Packet with valid CRC should validate")
|
||||
}
|
||||
|
||||
// Test packet with invalid CRC
|
||||
dataWithCRC[len(dataWithCRC)-1] ^= 0xFF // Corrupt CRC
|
||||
if ValidateCRC(dataWithCRC, 0x1234) {
|
||||
t.Error("Packet with invalid CRC should not validate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompression(t *testing.T) {
|
||||
// Test simple encoding (small packet)
|
||||
smallData := []byte{0x00, 0x09, 0x01, 0x02, 0x03}
|
||||
compressed, err := CompressPacket(smallData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if compressed[2] != CompressionFlagSimple {
|
||||
t.Errorf("Small packet should use simple encoding, got flag %02x", compressed[2])
|
||||
}
|
||||
|
||||
decompressed, err := DecompressPacket(compressed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decompressed, smallData) {
|
||||
t.Error("Decompressed data doesn't match original")
|
||||
}
|
||||
|
||||
// Test zlib compression (large packet)
|
||||
largeData := make([]byte, 100)
|
||||
largeData[0] = 0x00
|
||||
largeData[1] = 0x09
|
||||
for i := 2; i < len(largeData); i++ {
|
||||
largeData[i] = byte(i)
|
||||
}
|
||||
|
||||
compressed, err = CompressPacket(largeData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if compressed[2] != CompressionFlagZlib {
|
||||
t.Errorf("Large packet should use zlib compression, got flag %02x", compressed[2])
|
||||
}
|
||||
|
||||
decompressed, err = DecompressPacket(compressed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decompressed, largeData) {
|
||||
t.Error("Decompressed large data doesn't match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatEncryption(t *testing.T) {
|
||||
// Test chat encoding/decoding
|
||||
original := []byte{0x00, 0x09, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21} // "Hello!"
|
||||
key := uint32(0x12345678)
|
||||
|
||||
encoded := ChatEncode(original, key)
|
||||
if bytes.Equal(encoded[2:], original[2:]) {
|
||||
t.Error("Encoded data should differ from original")
|
||||
}
|
||||
|
||||
decoded := ChatDecode(encoded, key)
|
||||
if !bytes.Equal(decoded, original) {
|
||||
t.Errorf("Decoded data doesn't match original\nOriginal: %v\nDecoded: %v", original, decoded)
|
||||
}
|
||||
|
||||
// Test exempt packet types
|
||||
exemptPacket := []byte{0x00, 0x01, 0x12, 0x34}
|
||||
encoded = ChatEncode(exemptPacket, key)
|
||||
if !bytes.Equal(encoded, exemptPacket) {
|
||||
t.Error("Exempt packet should not be encoded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketCombiner(t *testing.T) {
|
||||
combiner := NewPacketCombiner(256)
|
||||
|
||||
p1 := NewEQProtocolPacket(OPPacket, []byte{0x01, 0x02})
|
||||
p2 := NewEQProtocolPacket(OPAck, []byte{0x03, 0x04})
|
||||
p3 := NewEQProtocolPacket(OPKeepAlive, []byte{0x05})
|
||||
|
||||
combined := combiner.CombineProtocolPackets([]*EQProtocolPacket{p1, p2, p3})
|
||||
|
||||
if combined == nil {
|
||||
t.Fatal("Failed to combine packets")
|
||||
}
|
||||
|
||||
if combined.Opcode != OPCombined {
|
||||
t.Errorf("Combined packet should have opcode %04x, got %04x", OPCombined, combined.Opcode)
|
||||
}
|
||||
|
||||
// Verify combined packet structure
|
||||
buffer := combined.Buffer
|
||||
offset := 0
|
||||
|
||||
// First packet
|
||||
if buffer[offset] != byte(p1.TotalSize()) {
|
||||
t.Errorf("First packet size incorrect: %d", buffer[offset])
|
||||
}
|
||||
offset++
|
||||
offset += int(p1.TotalSize())
|
||||
|
||||
// Second packet
|
||||
if buffer[offset] != byte(p2.TotalSize()) {
|
||||
t.Errorf("Second packet size incorrect: %d", buffer[offset])
|
||||
}
|
||||
offset++
|
||||
offset += int(p2.TotalSize())
|
||||
|
||||
// Third packet
|
||||
if buffer[offset] != byte(p3.TotalSize()) {
|
||||
t.Errorf("Third packet size incorrect: %d", buffer[offset])
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user