417 lines
11 KiB
Go
417 lines
11 KiB
Go
package structs
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestParseLoginRequest tests parsing the LoginRequest XML
|
|
func TestParseLoginRequest(t *testing.T) {
|
|
// Create a temporary directory with test XML
|
|
tmpDir := t.TempDir()
|
|
xmlDir := filepath.Join(tmpDir, "xml", "login")
|
|
os.MkdirAll(xmlDir, 0755)
|
|
|
|
// Write test LoginRequest XML
|
|
loginXML := `<packet name="LoginRequest">
|
|
<version number="1">
|
|
<str16 name="sessionID"/>
|
|
<str16 name="sessionRecycleToken"/>
|
|
<str16 name="username"/>
|
|
<str16 name="password"/>
|
|
<u32 name="acctNum"/>
|
|
<u32 name="passCode"/>
|
|
<u16 name="version"/>
|
|
</version>
|
|
<version number="562">
|
|
<str16 name="accesscode"/>
|
|
<str16 name="unknown1"/>
|
|
<str16 name="username"/>
|
|
<str16 name="password"/>
|
|
<u8 name="unknown2" size="8"/>
|
|
<u8 name="unknown3" size="2"/>
|
|
<u32 name="version"/>
|
|
<u16 name="unknown3b"/>
|
|
<u32 name="unknown4"/>
|
|
</version>
|
|
</packet>`
|
|
|
|
xmlFile := filepath.Join(xmlDir, "LoginRequest.xml")
|
|
if err := os.WriteFile(xmlFile, []byte(loginXML), 0644); err != nil {
|
|
t.Fatalf("Failed to write test XML: %v", err)
|
|
}
|
|
|
|
// Create parser and load file
|
|
parser := NewParser(tmpDir)
|
|
if err := parser.LoadFile(xmlFile); err != nil {
|
|
t.Fatalf("Failed to load XML: %v", err)
|
|
}
|
|
|
|
// Check packet was loaded
|
|
def, exists := parser.GetPacket("LoginRequest")
|
|
if !exists {
|
|
t.Fatal("LoginRequest packet not found")
|
|
}
|
|
|
|
if def.IsSubstruct {
|
|
t.Error("LoginRequest should not be a substruct")
|
|
}
|
|
|
|
// Check versions
|
|
if len(def.Versions) != 2 {
|
|
t.Errorf("Expected 2 versions, got %d", len(def.Versions))
|
|
}
|
|
|
|
// Check version 1 fields
|
|
if def.Versions[0].Version != 1 {
|
|
t.Errorf("Expected version 1, got %d", def.Versions[0].Version)
|
|
}
|
|
|
|
if len(def.Versions[0].Fields) != 7 {
|
|
t.Errorf("Expected 7 fields in version 1, got %d", len(def.Versions[0].Fields))
|
|
}
|
|
|
|
// Check specific fields
|
|
field := def.Versions[0].Fields[2]
|
|
if field.Name != "username" {
|
|
t.Errorf("Expected field name 'username', got '%s'", field.Name)
|
|
}
|
|
if field.Type != FieldTypeString16 {
|
|
t.Errorf("Expected field type String16, got %v", field.Type)
|
|
}
|
|
|
|
// Check version 562
|
|
if def.Versions[1].Version != 562 {
|
|
t.Errorf("Expected version 562, got %d", def.Versions[1].Version)
|
|
}
|
|
|
|
// Check array field
|
|
arrayField := def.Versions[1].Fields[4]
|
|
if arrayField.Name != "unknown2" {
|
|
t.Errorf("Expected field name 'unknown2', got '%s'", arrayField.Name)
|
|
}
|
|
if arrayField.Size != 8 {
|
|
t.Errorf("Expected size 8, got %d", arrayField.Size)
|
|
}
|
|
}
|
|
|
|
// TestPacketStruct tests packet struct serialization/deserialization
|
|
func TestPacketStruct(t *testing.T) {
|
|
// Create a simple packet version for testing
|
|
version := &PacketVersion{
|
|
Version: 1,
|
|
Fields: []Field{
|
|
{Name: "id", Type: FieldTypeUInt32},
|
|
{Name: "name", Type: FieldTypeString16},
|
|
{Name: "level", Type: FieldTypeUInt8},
|
|
{Name: "x", Type: FieldTypeFloat},
|
|
{Name: "y", Type: FieldTypeFloat},
|
|
{Name: "z", Type: FieldTypeFloat},
|
|
},
|
|
}
|
|
|
|
// Create packet struct
|
|
ps := NewPacketStruct(version)
|
|
|
|
// Set values
|
|
ps.Set("id", uint32(12345))
|
|
ps.Set("name", "TestPlayer")
|
|
ps.Set("level", uint8(50))
|
|
ps.Set("x", float32(100.5))
|
|
ps.Set("y", float32(200.5))
|
|
ps.Set("z", float32(300.5))
|
|
|
|
// Serialize
|
|
data, err := ps.Serialize()
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize: %v", err)
|
|
}
|
|
|
|
// Create new packet struct and deserialize
|
|
ps2 := NewPacketStruct(version)
|
|
if err := ps2.Deserialize(data); err != nil {
|
|
t.Fatalf("Failed to deserialize: %v", err)
|
|
}
|
|
|
|
// Check values
|
|
if val, _ := ps2.Get("id"); val.(uint32) != 12345 {
|
|
t.Errorf("Expected id 12345, got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("name"); val.(string) != "TestPlayer" {
|
|
t.Errorf("Expected name 'TestPlayer', got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("level"); val.(uint8) != 50 {
|
|
t.Errorf("Expected level 50, got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("x"); val.(float32) != 100.5 {
|
|
t.Errorf("Expected x 100.5, got %v", val)
|
|
}
|
|
}
|
|
|
|
// TestConfigReader tests the ConfigReader interface
|
|
func TestConfigReader(t *testing.T) {
|
|
// Create temporary directory with test XML
|
|
tmpDir := t.TempDir()
|
|
structsDir := filepath.Join(tmpDir, "structs", "xml", "test")
|
|
os.MkdirAll(structsDir, 0755)
|
|
|
|
// Write test packet XML
|
|
testXML := `<packet name="TestPacket">
|
|
<version number="1">
|
|
<u32 name="id"/>
|
|
<str16 name="name"/>
|
|
</version>
|
|
<version number="100">
|
|
<u32 name="id"/>
|
|
<str16 name="name"/>
|
|
<u8 name="level"/>
|
|
</version>
|
|
<version number="200">
|
|
<u32 name="id"/>
|
|
<str16 name="name"/>
|
|
<u8 name="level"/>
|
|
<u16 name="flags"/>
|
|
</version>
|
|
</packet>`
|
|
|
|
xmlFile := filepath.Join(structsDir, "TestPacket.xml")
|
|
if err := os.WriteFile(xmlFile, []byte(testXML), 0644); err != nil {
|
|
t.Fatalf("Failed to write test XML: %v", err)
|
|
}
|
|
|
|
// Create config reader
|
|
cr := NewConfigReader(tmpDir)
|
|
if err := cr.LoadFiles([]string{xmlFile}); err != nil {
|
|
t.Fatalf("Failed to load structs: %v", err)
|
|
}
|
|
|
|
// Test GetStruct with version selection
|
|
tests := []struct {
|
|
version uint16
|
|
expectedFields int
|
|
}{
|
|
{1, 2}, // Exact match
|
|
{50, 2}, // Should use version 1
|
|
{100, 3}, // Exact match
|
|
{150, 3}, // Should use version 100
|
|
{200, 4}, // Exact match
|
|
{300, 4}, // Should use version 200
|
|
}
|
|
|
|
for _, test := range tests {
|
|
ps, err := cr.GetStruct("TestPacket", test.version)
|
|
if err != nil {
|
|
t.Errorf("Failed to get struct for version %d: %v", test.version, err)
|
|
continue
|
|
}
|
|
|
|
if len(ps.definition.Fields) != test.expectedFields {
|
|
t.Errorf("Version %d: expected %d fields, got %d",
|
|
test.version, test.expectedFields, len(ps.definition.Fields))
|
|
}
|
|
}
|
|
|
|
// Test GetStructByVersion (exact match only)
|
|
ps, err := cr.GetStructByVersion("TestPacket", 100)
|
|
if err != nil {
|
|
t.Errorf("Failed to get struct by exact version: %v", err)
|
|
}
|
|
if ps != nil && len(ps.definition.Fields) != 3 {
|
|
t.Errorf("Expected 3 fields for version 100, got %d", len(ps.definition.Fields))
|
|
}
|
|
|
|
// This should fail (no exact match)
|
|
_, err = cr.GetStructByVersion("TestPacket", 150)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent exact version 150")
|
|
}
|
|
|
|
// Test GetStructVersion
|
|
version, err := cr.GetStructVersion("TestPacket", 150)
|
|
if err != nil {
|
|
t.Errorf("Failed to get struct version: %v", err)
|
|
}
|
|
if version != 100 {
|
|
t.Errorf("Expected version 100 for client version 150, got %d", version)
|
|
}
|
|
}
|
|
|
|
// TestStringFields tests various string field types
|
|
func TestStringFields(t *testing.T) {
|
|
version := &PacketVersion{
|
|
Version: 1,
|
|
Fields: []Field{
|
|
{Name: "str8", Type: FieldTypeString8},
|
|
{Name: "str16", Type: FieldTypeString16},
|
|
{Name: "str32", Type: FieldTypeString32},
|
|
{Name: "char_fixed", Type: FieldTypeChar, Size: 10},
|
|
{Name: "char_null", Type: FieldTypeChar, Size: 0},
|
|
},
|
|
}
|
|
|
|
ps := NewPacketStruct(version)
|
|
|
|
// Set string values
|
|
ps.Set("str8", "Short")
|
|
ps.Set("str16", "Medium length string")
|
|
ps.Set("str32", "This is a much longer string that might be used for descriptions")
|
|
ps.Set("char_fixed", "Fixed")
|
|
ps.Set("char_null", "Null-term")
|
|
|
|
// Serialize
|
|
data, err := ps.Serialize()
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize: %v", err)
|
|
}
|
|
|
|
// Deserialize
|
|
ps2 := NewPacketStruct(version)
|
|
if err := ps2.Deserialize(data); err != nil {
|
|
t.Fatalf("Failed to deserialize: %v", err)
|
|
}
|
|
|
|
// Verify strings
|
|
if val, _ := ps2.Get("str8"); val.(string) != "Short" {
|
|
t.Errorf("str8 mismatch: got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("str16"); val.(string) != "Medium length string" {
|
|
t.Errorf("str16 mismatch: got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("str32"); val.(string) != "This is a much longer string that might be used for descriptions" {
|
|
t.Errorf("str32 mismatch: got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("char_fixed"); val.(string) != "Fixed" {
|
|
t.Errorf("char_fixed mismatch: got %v", val)
|
|
}
|
|
|
|
if val, _ := ps2.Get("char_null"); val.(string) != "Null-term" {
|
|
t.Errorf("char_null mismatch: got %v", val)
|
|
}
|
|
}
|
|
|
|
// TestConditionalFields tests fields with if-set/if-not-set conditions
|
|
func TestConditionalFields(t *testing.T) {
|
|
version := &PacketVersion{
|
|
Version: 1,
|
|
Fields: []Field{
|
|
{Name: "hasData", Type: FieldTypeUInt8},
|
|
{Name: "data", Type: FieldTypeUInt32, IfSet: "hasData"},
|
|
{Name: "noData", Type: FieldTypeUInt16, IfNotSet: "hasData"},
|
|
},
|
|
}
|
|
|
|
// Test with hasData set
|
|
ps1 := NewPacketStruct(version)
|
|
ps1.Set("hasData", uint8(1))
|
|
ps1.Set("data", uint32(999))
|
|
ps1.Set("noData", uint16(111))
|
|
|
|
data1, err := ps1.Serialize()
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize: %v", err)
|
|
}
|
|
|
|
// Should contain: uint8(1), uint32(999)
|
|
// Should NOT contain: uint16(111)
|
|
expectedSize := 1 + 4
|
|
if len(data1) != expectedSize {
|
|
t.Errorf("Expected serialized size %d, got %d", expectedSize, len(data1))
|
|
}
|
|
|
|
// Test with hasData not set
|
|
ps2 := NewPacketStruct(version)
|
|
ps2.Set("data", uint32(999))
|
|
ps2.Set("noData", uint16(111))
|
|
|
|
data2, err := ps2.Serialize()
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize: %v", err)
|
|
}
|
|
|
|
// Should contain: uint8(0), uint16(111)
|
|
// Should NOT contain: uint32(999)
|
|
expectedSize = 1 + 2
|
|
if len(data2) != expectedSize {
|
|
t.Errorf("Expected serialized size %d, got %d", expectedSize, len(data2))
|
|
}
|
|
}
|
|
|
|
// BenchmarkSerialization benchmarks packet serialization
|
|
func BenchmarkSerialization(b *testing.B) {
|
|
version := &PacketVersion{
|
|
Version: 1,
|
|
Fields: []Field{
|
|
{Name: "id", Type: FieldTypeUInt32},
|
|
{Name: "name", Type: FieldTypeString16},
|
|
{Name: "level", Type: FieldTypeUInt8},
|
|
{Name: "x", Type: FieldTypeFloat},
|
|
{Name: "y", Type: FieldTypeFloat},
|
|
{Name: "z", Type: FieldTypeFloat},
|
|
{Name: "flags", Type: FieldTypeUInt32},
|
|
{Name: "timestamp", Type: FieldTypeUInt64},
|
|
},
|
|
}
|
|
|
|
ps := NewPacketStruct(version)
|
|
ps.Set("id", uint32(12345))
|
|
ps.Set("name", "BenchmarkPlayer")
|
|
ps.Set("level", uint8(50))
|
|
ps.Set("x", float32(100.5))
|
|
ps.Set("y", float32(200.5))
|
|
ps.Set("z", float32(300.5))
|
|
ps.Set("flags", uint32(0xFF00FF00))
|
|
ps.Set("timestamp", uint64(1234567890))
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
data, err := ps.Serialize()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
_ = data
|
|
}
|
|
}
|
|
|
|
// BenchmarkDeserialization benchmarks packet deserialization
|
|
func BenchmarkDeserialization(b *testing.B) {
|
|
version := &PacketVersion{
|
|
Version: 1,
|
|
Fields: []Field{
|
|
{Name: "id", Type: FieldTypeUInt32},
|
|
{Name: "name", Type: FieldTypeString16},
|
|
{Name: "level", Type: FieldTypeUInt8},
|
|
{Name: "x", Type: FieldTypeFloat},
|
|
{Name: "y", Type: FieldTypeFloat},
|
|
{Name: "z", Type: FieldTypeFloat},
|
|
},
|
|
}
|
|
|
|
// Create test data
|
|
ps := NewPacketStruct(version)
|
|
ps.Set("id", uint32(12345))
|
|
ps.Set("name", "TestPlayer")
|
|
ps.Set("level", uint8(50))
|
|
ps.Set("x", float32(100.5))
|
|
ps.Set("y", float32(200.5))
|
|
ps.Set("z", float32(300.5))
|
|
|
|
data, _ := ps.Serialize()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
ps2 := NewPacketStruct(version)
|
|
err := ps2.Deserialize(data)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
} |