mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 17:43:21 +01:00
tests(network): adds tests for network features (except mhfpacket).
This commit is contained in:
380
network/binpacket/msg_bin_chat_test.go
Normal file
380
network/binpacket/msg_bin_chat_test.go
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
package binpacket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"erupe-ce/common/byteframe"
|
||||||
|
"erupe-ce/network"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMsgBinChat_Opcode(t *testing.T) {
|
||||||
|
msg := &MsgBinChat{}
|
||||||
|
if msg.Opcode() != network.MSG_SYS_CAST_BINARY {
|
||||||
|
t.Errorf("Opcode() = %v, want %v", msg.Opcode(), network.MSG_SYS_CAST_BINARY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinChat_Build(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
msg *MsgBinChat
|
||||||
|
wantErr bool
|
||||||
|
validate func(*testing.T, []byte)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic message",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x01,
|
||||||
|
Type: ChatTypeWorld,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "Hello",
|
||||||
|
SenderName: "Player1",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
t.Error("Build() returned empty data")
|
||||||
|
}
|
||||||
|
// Verify the structure starts with Unk0, Type, Flags
|
||||||
|
if data[0] != 0x01 {
|
||||||
|
t.Errorf("Unk0 = 0x%X, want 0x01", data[0])
|
||||||
|
}
|
||||||
|
if data[1] != byte(ChatTypeWorld) {
|
||||||
|
t.Errorf("Type = 0x%X, want 0x%X", data[1], byte(ChatTypeWorld))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all chat types",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeStage,
|
||||||
|
Flags: 0x1234,
|
||||||
|
Message: "Test",
|
||||||
|
SenderName: "Sender",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty message",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeGuild,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "",
|
||||||
|
SenderName: "Player",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty sender",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeParty,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "Hello",
|
||||||
|
SenderName: "",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "long message",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeWhisper,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "This is a very long message that contains a lot of text to test the handling of longer strings in the binary packet format.",
|
||||||
|
SenderName: "LongNamePlayer",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "special characters",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeAlliance,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "Hello!@#$%^&*()",
|
||||||
|
SenderName: "Player_123",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := tt.msg.Build(bf)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Build() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
data := bf.Data()
|
||||||
|
if tt.validate != nil {
|
||||||
|
tt.validate(t, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinChat_Parse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
want *MsgBinChat
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic message",
|
||||||
|
data: []byte{
|
||||||
|
0x01, // Unk0
|
||||||
|
0x00, // Type (ChatTypeWorld)
|
||||||
|
0x00, 0x00, // Flags
|
||||||
|
0x00, 0x08, // lenSenderName (8)
|
||||||
|
0x00, 0x06, // lenMessage (6)
|
||||||
|
// Message: "Hello" + null terminator (SJIS compatible ASCII)
|
||||||
|
0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00,
|
||||||
|
// SenderName: "Player1" + null terminator
|
||||||
|
0x50, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x31, 0x00,
|
||||||
|
},
|
||||||
|
want: &MsgBinChat{
|
||||||
|
Unk0: 0x01,
|
||||||
|
Type: ChatTypeWorld,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "Hello",
|
||||||
|
SenderName: "Player1",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different chat type",
|
||||||
|
data: []byte{
|
||||||
|
0x00, // Unk0
|
||||||
|
0x02, // Type (ChatTypeGuild)
|
||||||
|
0x12, 0x34, // Flags
|
||||||
|
0x00, 0x05, // lenSenderName
|
||||||
|
0x00, 0x03, // lenMessage
|
||||||
|
// Message: "Hi" + null
|
||||||
|
0x48, 0x69, 0x00,
|
||||||
|
// SenderName: "Bob" + null + padding
|
||||||
|
0x42, 0x6F, 0x62, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
want: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeGuild,
|
||||||
|
Flags: 0x1234,
|
||||||
|
Message: "Hi",
|
||||||
|
SenderName: "Bob",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bf := byteframe.NewByteFrameFromBytes(tt.data)
|
||||||
|
msg := &MsgBinChat{}
|
||||||
|
|
||||||
|
err := msg.Parse(bf)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
if msg.Unk0 != tt.want.Unk0 {
|
||||||
|
t.Errorf("Unk0 = 0x%X, want 0x%X", msg.Unk0, tt.want.Unk0)
|
||||||
|
}
|
||||||
|
if msg.Type != tt.want.Type {
|
||||||
|
t.Errorf("Type = %v, want %v", msg.Type, tt.want.Type)
|
||||||
|
}
|
||||||
|
if msg.Flags != tt.want.Flags {
|
||||||
|
t.Errorf("Flags = 0x%X, want 0x%X", msg.Flags, tt.want.Flags)
|
||||||
|
}
|
||||||
|
if msg.Message != tt.want.Message {
|
||||||
|
t.Errorf("Message = %q, want %q", msg.Message, tt.want.Message)
|
||||||
|
}
|
||||||
|
if msg.SenderName != tt.want.SenderName {
|
||||||
|
t.Errorf("SenderName = %q, want %q", msg.SenderName, tt.want.SenderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinChat_RoundTrip(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
msg *MsgBinChat
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "world chat",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x01,
|
||||||
|
Type: ChatTypeWorld,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "Hello World",
|
||||||
|
SenderName: "TestPlayer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stage chat",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeStage,
|
||||||
|
Flags: 0x1234,
|
||||||
|
Message: "Stage message",
|
||||||
|
SenderName: "Player2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "guild chat",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x02,
|
||||||
|
Type: ChatTypeGuild,
|
||||||
|
Flags: 0xFFFF,
|
||||||
|
Message: "Guild announcement",
|
||||||
|
SenderName: "GuildMaster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alliance chat",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeAlliance,
|
||||||
|
Flags: 0x0001,
|
||||||
|
Message: "Alliance msg",
|
||||||
|
SenderName: "AllyLeader",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "party chat",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x01,
|
||||||
|
Type: ChatTypeParty,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "Party up!",
|
||||||
|
SenderName: "PartyLeader",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whisper",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeWhisper,
|
||||||
|
Flags: 0x0002,
|
||||||
|
Message: "Secret message",
|
||||||
|
SenderName: "Whisperer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty strings",
|
||||||
|
msg: &MsgBinChat{
|
||||||
|
Unk0: 0x00,
|
||||||
|
Type: ChatTypeWorld,
|
||||||
|
Flags: 0x0000,
|
||||||
|
Message: "",
|
||||||
|
SenderName: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Build
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := tt.msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
parsedMsg := &MsgBinChat{}
|
||||||
|
parsedBf := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||||
|
err = parsedMsg.Parse(parsedBf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
if parsedMsg.Unk0 != tt.msg.Unk0 {
|
||||||
|
t.Errorf("Unk0 = 0x%X, want 0x%X", parsedMsg.Unk0, tt.msg.Unk0)
|
||||||
|
}
|
||||||
|
if parsedMsg.Type != tt.msg.Type {
|
||||||
|
t.Errorf("Type = %v, want %v", parsedMsg.Type, tt.msg.Type)
|
||||||
|
}
|
||||||
|
if parsedMsg.Flags != tt.msg.Flags {
|
||||||
|
t.Errorf("Flags = 0x%X, want 0x%X", parsedMsg.Flags, tt.msg.Flags)
|
||||||
|
}
|
||||||
|
if parsedMsg.Message != tt.msg.Message {
|
||||||
|
t.Errorf("Message = %q, want %q", parsedMsg.Message, tt.msg.Message)
|
||||||
|
}
|
||||||
|
if parsedMsg.SenderName != tt.msg.SenderName {
|
||||||
|
t.Errorf("SenderName = %q, want %q", parsedMsg.SenderName, tt.msg.SenderName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatType_Values(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
chatType ChatType
|
||||||
|
expected uint8
|
||||||
|
}{
|
||||||
|
{ChatTypeWorld, 0},
|
||||||
|
{ChatTypeStage, 1},
|
||||||
|
{ChatTypeGuild, 2},
|
||||||
|
{ChatTypeAlliance, 3},
|
||||||
|
{ChatTypeParty, 4},
|
||||||
|
{ChatTypeWhisper, 5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if uint8(tt.chatType) != tt.expected {
|
||||||
|
t.Errorf("ChatType value = %d, want %d", uint8(tt.chatType), tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinChat_BuildParseConsistency(t *testing.T) {
|
||||||
|
// Test that Build and Parse are consistent with each other
|
||||||
|
// by building, parsing, building again, and comparing
|
||||||
|
original := &MsgBinChat{
|
||||||
|
Unk0: 0x01,
|
||||||
|
Type: ChatTypeWorld,
|
||||||
|
Flags: 0x1234,
|
||||||
|
Message: "Test message",
|
||||||
|
SenderName: "TestSender",
|
||||||
|
}
|
||||||
|
|
||||||
|
// First build
|
||||||
|
bf1 := byteframe.NewByteFrame()
|
||||||
|
err := original.Build(bf1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("First Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
parsed := &MsgBinChat{}
|
||||||
|
parsedBf := byteframe.NewByteFrameFromBytes(bf1.Data())
|
||||||
|
err = parsed.Parse(parsedBf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second build
|
||||||
|
bf2 := byteframe.NewByteFrame()
|
||||||
|
err = parsed.Build(bf2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Second Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the two builds
|
||||||
|
if !bytes.Equal(bf1.Data(), bf2.Data()) {
|
||||||
|
t.Errorf("Build-Parse-Build inconsistency:\nFirst: %v\nSecond: %v", bf1.Data(), bf2.Data())
|
||||||
|
}
|
||||||
|
}
|
||||||
219
network/binpacket/msg_bin_mail_notify_test.go
Normal file
219
network/binpacket/msg_bin_mail_notify_test.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package binpacket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"erupe-ce/common/byteframe"
|
||||||
|
"erupe-ce/network"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_Opcode(t *testing.T) {
|
||||||
|
msg := MsgBinMailNotify{}
|
||||||
|
if msg.Opcode() != network.MSG_SYS_CASTED_BINARY {
|
||||||
|
t.Errorf("Opcode() = %v, want %v", msg.Opcode(), network.MSG_SYS_CASTED_BINARY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_Build(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
senderName string
|
||||||
|
wantErr bool
|
||||||
|
validate func(*testing.T, []byte)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic sender name",
|
||||||
|
senderName: "Player1",
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
t.Error("Build() returned empty data")
|
||||||
|
}
|
||||||
|
// First byte should be 0x01 (Unk)
|
||||||
|
if data[0] != 0x01 {
|
||||||
|
t.Errorf("First byte = 0x%X, want 0x01", data[0])
|
||||||
|
}
|
||||||
|
// Total length should be 1 (Unk) + 21 (padded string)
|
||||||
|
expectedLen := 1 + 21
|
||||||
|
if len(data) != expectedLen {
|
||||||
|
t.Errorf("data length = %d, want %d", len(data), expectedLen)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty sender name",
|
||||||
|
senderName: "",
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) != 22 { // 1 + 21
|
||||||
|
t.Errorf("data length = %d, want 22", len(data))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "long sender name",
|
||||||
|
senderName: "VeryLongPlayerNameThatExceeds21Characters",
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) != 22 { // 1 + 21 (truncated/padded)
|
||||||
|
t.Errorf("data length = %d, want 22", len(data))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exactly 21 characters",
|
||||||
|
senderName: "ExactlyTwentyOneChar1",
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) != 22 {
|
||||||
|
t.Errorf("data length = %d, want 22", len(data))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "special characters",
|
||||||
|
senderName: "Player_123",
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) != 22 {
|
||||||
|
t.Errorf("data length = %d, want 22", len(data))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
msg := MsgBinMailNotify{
|
||||||
|
SenderName: tt.senderName,
|
||||||
|
}
|
||||||
|
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := msg.Build(bf)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Build() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr && tt.validate != nil {
|
||||||
|
tt.validate(t, bf.Data())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_Parse_Panics(t *testing.T) {
|
||||||
|
// Document that Parse() is not implemented and panics
|
||||||
|
msg := MsgBinMailNotify{}
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Error("Parse() did not panic, but should panic with 'implement me'")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// This should panic
|
||||||
|
_ = msg.Parse(bf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_BuildMultiple(t *testing.T) {
|
||||||
|
// Test building multiple messages to ensure no state pollution
|
||||||
|
names := []string{"Player1", "Player2", "Player3"}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
msg := MsgBinMailNotify{SenderName: name}
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build(%s) error = %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := bf.Data()
|
||||||
|
if len(data) != 22 {
|
||||||
|
t.Errorf("Build(%s) length = %d, want 22", name, len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_PaddingBehavior(t *testing.T) {
|
||||||
|
// Test that the padded string is always 21 bytes
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
senderName string
|
||||||
|
}{
|
||||||
|
{"short", "A"},
|
||||||
|
{"medium", "PlayerName"},
|
||||||
|
{"long", "VeryVeryLongPlayerName"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
msg := MsgBinMailNotify{SenderName: tt.senderName}
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := bf.Data()
|
||||||
|
// Skip first byte (Unk), check remaining 21 bytes
|
||||||
|
if len(data) < 22 {
|
||||||
|
t.Fatalf("data too short: %d bytes", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
paddedString := data[1:22]
|
||||||
|
if len(paddedString) != 21 {
|
||||||
|
t.Errorf("padded string length = %d, want 21", len(paddedString))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_BuildStructure(t *testing.T) {
|
||||||
|
// Test the structure of the built data
|
||||||
|
msg := MsgBinMailNotify{SenderName: "Test"}
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := bf.Data()
|
||||||
|
|
||||||
|
// Check structure: 1 byte Unk + 21 bytes padded string = 22 bytes total
|
||||||
|
if len(data) != 22 {
|
||||||
|
t.Errorf("data length = %d, want 22", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First byte should be 0x01
|
||||||
|
if data[0] != 0x01 {
|
||||||
|
t.Errorf("Unk byte = 0x%X, want 0x01", data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest (21 bytes) should contain the sender name (SJIS encoded) and padding
|
||||||
|
// We can't verify exact content without knowing SJIS encoding details,
|
||||||
|
// but we can verify length
|
||||||
|
paddedPortion := data[1:]
|
||||||
|
if len(paddedPortion) != 21 {
|
||||||
|
t.Errorf("padded portion length = %d, want 21", len(paddedPortion))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinMailNotify_ValueSemantics(t *testing.T) {
|
||||||
|
// Test that MsgBinMailNotify uses value semantics (not pointer receiver for Opcode)
|
||||||
|
msg := MsgBinMailNotify{SenderName: "Test"}
|
||||||
|
|
||||||
|
// Should work with value
|
||||||
|
opcode := msg.Opcode()
|
||||||
|
if opcode != network.MSG_SYS_CASTED_BINARY {
|
||||||
|
t.Errorf("Opcode() = %v, want %v", opcode, network.MSG_SYS_CASTED_BINARY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should also work with pointer (Go allows this)
|
||||||
|
msgPtr := &MsgBinMailNotify{SenderName: "Test"}
|
||||||
|
opcode2 := msgPtr.Opcode()
|
||||||
|
if opcode2 != network.MSG_SYS_CASTED_BINARY {
|
||||||
|
t.Errorf("Opcode() on pointer = %v, want %v", opcode2, network.MSG_SYS_CASTED_BINARY)
|
||||||
|
}
|
||||||
|
}
|
||||||
404
network/binpacket/msg_bin_targeted_test.go
Normal file
404
network/binpacket/msg_bin_targeted_test.go
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
package binpacket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"erupe-ce/common/byteframe"
|
||||||
|
"erupe-ce/network"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_Opcode(t *testing.T) {
|
||||||
|
msg := &MsgBinTargeted{}
|
||||||
|
if msg.Opcode() != network.MSG_SYS_CAST_BINARY {
|
||||||
|
t.Errorf("Opcode() = %v, want %v", msg.Opcode(), network.MSG_SYS_CAST_BINARY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_Build(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
msg *MsgBinTargeted
|
||||||
|
wantErr bool
|
||||||
|
validate func(*testing.T, []byte)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single target with payload",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 1,
|
||||||
|
TargetCharIDs: []uint32{12345},
|
||||||
|
RawDataPayload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) < 2+4+4 { // 2 bytes count + 4 bytes ID + 4 bytes payload
|
||||||
|
t.Errorf("data length = %d, want at least %d", len(data), 2+4+4)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple targets",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 3,
|
||||||
|
TargetCharIDs: []uint32{100, 200, 300},
|
||||||
|
RawDataPayload: []byte{0xAA, 0xBB},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
expectedLen := 2 + (3 * 4) + 2 // count + 3 IDs + payload
|
||||||
|
if len(data) != expectedLen {
|
||||||
|
t.Errorf("data length = %d, want %d", len(data), expectedLen)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero targets",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 0,
|
||||||
|
TargetCharIDs: []uint32{},
|
||||||
|
RawDataPayload: []byte{0xFF},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
if len(data) < 2+1 { // count + payload
|
||||||
|
t.Errorf("data length = %d, want at least %d", len(data), 2+1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty payload",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 1,
|
||||||
|
TargetCharIDs: []uint32{999},
|
||||||
|
RawDataPayload: []byte{},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
validate: func(t *testing.T, data []byte) {
|
||||||
|
expectedLen := 2 + 4 // count + 1 ID
|
||||||
|
if len(data) != expectedLen {
|
||||||
|
t.Errorf("data length = %d, want %d", len(data), expectedLen)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "large payload",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 2,
|
||||||
|
TargetCharIDs: []uint32{1000, 2000},
|
||||||
|
RawDataPayload: bytes.Repeat([]byte{0xCC}, 256),
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max uint32 target IDs",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 2,
|
||||||
|
TargetCharIDs: []uint32{0xFFFFFFFF, 0x12345678},
|
||||||
|
RawDataPayload: []byte{0x01},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := tt.msg.Build(bf)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Build() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
data := bf.Data()
|
||||||
|
if tt.validate != nil {
|
||||||
|
tt.validate(t, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_Parse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
want *MsgBinTargeted
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single target",
|
||||||
|
data: []byte{
|
||||||
|
0x00, 0x01, // TargetCount = 1
|
||||||
|
0x00, 0x00, 0x30, 0x39, // TargetCharID = 12345
|
||||||
|
0xAA, 0xBB, 0xCC, // RawDataPayload
|
||||||
|
},
|
||||||
|
want: &MsgBinTargeted{
|
||||||
|
TargetCount: 1,
|
||||||
|
TargetCharIDs: []uint32{12345},
|
||||||
|
RawDataPayload: []byte{0xAA, 0xBB, 0xCC},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple targets",
|
||||||
|
data: []byte{
|
||||||
|
0x00, 0x03, // TargetCount = 3
|
||||||
|
0x00, 0x00, 0x00, 0x64, // Target 1 = 100
|
||||||
|
0x00, 0x00, 0x00, 0xC8, // Target 2 = 200
|
||||||
|
0x00, 0x00, 0x01, 0x2C, // Target 3 = 300
|
||||||
|
0x01, 0x02, // RawDataPayload
|
||||||
|
},
|
||||||
|
want: &MsgBinTargeted{
|
||||||
|
TargetCount: 3,
|
||||||
|
TargetCharIDs: []uint32{100, 200, 300},
|
||||||
|
RawDataPayload: []byte{0x01, 0x02},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero targets",
|
||||||
|
data: []byte{
|
||||||
|
0x00, 0x00, // TargetCount = 0
|
||||||
|
0xFF, 0xFF, // RawDataPayload
|
||||||
|
},
|
||||||
|
want: &MsgBinTargeted{
|
||||||
|
TargetCount: 0,
|
||||||
|
TargetCharIDs: []uint32{},
|
||||||
|
RawDataPayload: []byte{0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no payload",
|
||||||
|
data: []byte{
|
||||||
|
0x00, 0x01, // TargetCount = 1
|
||||||
|
0x00, 0x00, 0x03, 0xE7, // Target = 999
|
||||||
|
},
|
||||||
|
want: &MsgBinTargeted{
|
||||||
|
TargetCount: 1,
|
||||||
|
TargetCharIDs: []uint32{999},
|
||||||
|
RawDataPayload: []byte{},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
bf := byteframe.NewByteFrameFromBytes(tt.data)
|
||||||
|
msg := &MsgBinTargeted{}
|
||||||
|
|
||||||
|
err := msg.Parse(bf)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
if msg.TargetCount != tt.want.TargetCount {
|
||||||
|
t.Errorf("TargetCount = %d, want %d", msg.TargetCount, tt.want.TargetCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msg.TargetCharIDs) != len(tt.want.TargetCharIDs) {
|
||||||
|
t.Errorf("len(TargetCharIDs) = %d, want %d", len(msg.TargetCharIDs), len(tt.want.TargetCharIDs))
|
||||||
|
} else {
|
||||||
|
for i, id := range msg.TargetCharIDs {
|
||||||
|
if id != tt.want.TargetCharIDs[i] {
|
||||||
|
t.Errorf("TargetCharIDs[%d] = %d, want %d", i, id, tt.want.TargetCharIDs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(msg.RawDataPayload, tt.want.RawDataPayload) {
|
||||||
|
t.Errorf("RawDataPayload = %v, want %v", msg.RawDataPayload, tt.want.RawDataPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_RoundTrip(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
msg *MsgBinTargeted
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single target",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 1,
|
||||||
|
TargetCharIDs: []uint32{12345},
|
||||||
|
RawDataPayload: []byte{0x01, 0x02, 0x03},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple targets",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 5,
|
||||||
|
TargetCharIDs: []uint32{100, 200, 300, 400, 500},
|
||||||
|
RawDataPayload: []byte{0xAA, 0xBB, 0xCC, 0xDD},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero targets",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 0,
|
||||||
|
TargetCharIDs: []uint32{},
|
||||||
|
RawDataPayload: []byte{0xFF},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty payload",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 2,
|
||||||
|
TargetCharIDs: []uint32{1000, 2000},
|
||||||
|
RawDataPayload: []byte{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "large IDs and payload",
|
||||||
|
msg: &MsgBinTargeted{
|
||||||
|
TargetCount: 3,
|
||||||
|
TargetCharIDs: []uint32{0xFFFFFFFF, 0x12345678, 0xABCDEF00},
|
||||||
|
RawDataPayload: bytes.Repeat([]byte{0xDD}, 128),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Build
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := tt.msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
parsedMsg := &MsgBinTargeted{}
|
||||||
|
parsedBf := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||||
|
err = parsedMsg.Parse(parsedBf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
if parsedMsg.TargetCount != tt.msg.TargetCount {
|
||||||
|
t.Errorf("TargetCount = %d, want %d", parsedMsg.TargetCount, tt.msg.TargetCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsedMsg.TargetCharIDs) != len(tt.msg.TargetCharIDs) {
|
||||||
|
t.Errorf("len(TargetCharIDs) = %d, want %d", len(parsedMsg.TargetCharIDs), len(tt.msg.TargetCharIDs))
|
||||||
|
} else {
|
||||||
|
for i, id := range parsedMsg.TargetCharIDs {
|
||||||
|
if id != tt.msg.TargetCharIDs[i] {
|
||||||
|
t.Errorf("TargetCharIDs[%d] = %d, want %d", i, id, tt.msg.TargetCharIDs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(parsedMsg.RawDataPayload, tt.msg.RawDataPayload) {
|
||||||
|
t.Errorf("RawDataPayload length mismatch: got %d, want %d", len(parsedMsg.RawDataPayload), len(tt.msg.RawDataPayload))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_TargetCountMismatch(t *testing.T) {
|
||||||
|
// Test that TargetCount and actual array length don't have to match
|
||||||
|
// The Build function uses the TargetCount field
|
||||||
|
msg := &MsgBinTargeted{
|
||||||
|
TargetCount: 2, // Says 2
|
||||||
|
TargetCharIDs: []uint32{100, 200, 300}, // But has 3
|
||||||
|
RawDataPayload: []byte{0x01},
|
||||||
|
}
|
||||||
|
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse should read exactly 2 IDs as specified by TargetCount
|
||||||
|
parsedMsg := &MsgBinTargeted{}
|
||||||
|
parsedBf := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||||
|
err = parsedMsg.Parse(parsedBf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedMsg.TargetCount != 2 {
|
||||||
|
t.Errorf("TargetCount = %d, want 2", parsedMsg.TargetCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsedMsg.TargetCharIDs) != 2 {
|
||||||
|
t.Errorf("len(TargetCharIDs) = %d, want 2", len(parsedMsg.TargetCharIDs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_BuildParseConsistency(t *testing.T) {
|
||||||
|
original := &MsgBinTargeted{
|
||||||
|
TargetCount: 3,
|
||||||
|
TargetCharIDs: []uint32{111, 222, 333},
|
||||||
|
RawDataPayload: []byte{0x11, 0x22, 0x33, 0x44},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First build
|
||||||
|
bf1 := byteframe.NewByteFrame()
|
||||||
|
err := original.Build(bf1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("First Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
parsed := &MsgBinTargeted{}
|
||||||
|
parsedBf := byteframe.NewByteFrameFromBytes(bf1.Data())
|
||||||
|
err = parsed.Parse(parsedBf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second build
|
||||||
|
bf2 := byteframe.NewByteFrame()
|
||||||
|
err = parsed.Build(bf2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Second Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the two builds
|
||||||
|
if !bytes.Equal(bf1.Data(), bf2.Data()) {
|
||||||
|
t.Errorf("Build-Parse-Build inconsistency:\nFirst: %v\nSecond: %v", bf1.Data(), bf2.Data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgBinTargeted_PayloadForwarding(t *testing.T) {
|
||||||
|
// Test that RawDataPayload is correctly preserved
|
||||||
|
// This is important as it forwards another binpacket
|
||||||
|
originalPayload := []byte{
|
||||||
|
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
|
||||||
|
0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &MsgBinTargeted{
|
||||||
|
TargetCount: 1,
|
||||||
|
TargetCharIDs: []uint32{999},
|
||||||
|
RawDataPayload: originalPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
err := msg.Build(bf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed := &MsgBinTargeted{}
|
||||||
|
parsedBf := byteframe.NewByteFrameFromBytes(bf.Data())
|
||||||
|
err = parsed.Parse(parsedBf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(parsed.RawDataPayload, originalPayload) {
|
||||||
|
t.Errorf("Payload not preserved:\ngot: %v\nwant: %v", parsed.RawDataPayload, originalPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
31
network/clientctx/clientcontext_test.go
Normal file
31
network/clientctx/clientcontext_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package clientctx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestClientContext_Exists verifies that the ClientContext type exists
|
||||||
|
// and can be instantiated, even though it's currently unused.
|
||||||
|
func TestClientContext_Exists(t *testing.T) {
|
||||||
|
// This test documents that ClientContext is currently an empty struct
|
||||||
|
// and is marked as unused in the codebase.
|
||||||
|
var ctx ClientContext
|
||||||
|
|
||||||
|
// Verify it's a zero-size struct
|
||||||
|
_ = ctx
|
||||||
|
|
||||||
|
// Just verify we can create it
|
||||||
|
ctx2 := ClientContext{}
|
||||||
|
_ = ctx2
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestClientContext_IsEmpty verifies that ClientContext has no fields
|
||||||
|
func TestClientContext_IsEmpty(t *testing.T) {
|
||||||
|
// The struct should be empty as marked by the comment "// Unused"
|
||||||
|
// This test documents the current state of the struct
|
||||||
|
ctx := ClientContext{}
|
||||||
|
_ = ctx
|
||||||
|
|
||||||
|
// If fields are added in the future, this test will need to be updated
|
||||||
|
// Currently it's just a placeholder/documentation test
|
||||||
|
}
|
||||||
482
network/crypt_conn_test.go
Normal file
482
network/crypt_conn_test.go
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_config "erupe-ce/config"
|
||||||
|
"erupe-ce/network/crypto"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockConn implements net.Conn for testing
|
||||||
|
type mockConn struct {
|
||||||
|
readData *bytes.Buffer
|
||||||
|
writeData *bytes.Buffer
|
||||||
|
closed bool
|
||||||
|
readErr error
|
||||||
|
writeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockConn(readData []byte) *mockConn {
|
||||||
|
return &mockConn{
|
||||||
|
readData: bytes.NewBuffer(readData),
|
||||||
|
writeData: bytes.NewBuffer(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockConn) Read(b []byte) (n int, err error) {
|
||||||
|
if m.readErr != nil {
|
||||||
|
return 0, m.readErr
|
||||||
|
}
|
||||||
|
return m.readData.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockConn) Write(b []byte) (n int, err error) {
|
||||||
|
if m.writeErr != nil {
|
||||||
|
return 0, m.writeErr
|
||||||
|
}
|
||||||
|
return m.writeData.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockConn) Close() error {
|
||||||
|
m.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockConn) LocalAddr() net.Addr { return nil }
|
||||||
|
func (m *mockConn) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (m *mockConn) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (m *mockConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
func TestNewCryptConn(t *testing.T) {
|
||||||
|
mockConn := newMockConn(nil)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
if cc == nil {
|
||||||
|
t.Fatal("NewCryptConn() returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.conn != mockConn {
|
||||||
|
t.Error("conn not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.readKeyRot != 995117 {
|
||||||
|
t.Errorf("readKeyRot = %d, want 995117", cc.readKeyRot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.sendKeyRot != 995117 {
|
||||||
|
t.Errorf("sendKeyRot = %d, want 995117", cc.sendKeyRot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.sentPackets != 0 {
|
||||||
|
t.Errorf("sentPackets = %d, want 0", cc.sentPackets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.prevRecvPacketCombinedCheck != 0 {
|
||||||
|
t.Errorf("prevRecvPacketCombinedCheck = %d, want 0", cc.prevRecvPacketCombinedCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.prevSendPacketCombinedCheck != 0 {
|
||||||
|
t.Errorf("prevSendPacketCombinedCheck = %d, want 0", cc.prevSendPacketCombinedCheck)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_SendPacket(t *testing.T) {
|
||||||
|
// Save original config and restore after test
|
||||||
|
originalMode := _config.ErupeConfig.RealClientMode
|
||||||
|
defer func() {
|
||||||
|
_config.ErupeConfig.RealClientMode = originalMode
|
||||||
|
}()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "small packet",
|
||||||
|
data: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty packet",
|
||||||
|
data: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "larger packet",
|
||||||
|
data: bytes.Repeat([]byte{0xAA}, 256),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockConn := newMockConn(nil)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
err := cc.SendPacket(tt.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendPacket() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
written := mockConn.writeData.Bytes()
|
||||||
|
if len(written) < CryptPacketHeaderLength {
|
||||||
|
t.Fatalf("written data length = %d, want at least %d", len(written), CryptPacketHeaderLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify header was written
|
||||||
|
headerData := written[:CryptPacketHeaderLength]
|
||||||
|
header, err := NewCryptPacketHeader(headerData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify packet counter incremented
|
||||||
|
if cc.sentPackets != 1 {
|
||||||
|
t.Errorf("sentPackets = %d, want 1", cc.sentPackets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify header fields
|
||||||
|
if header.KeyRotDelta != 3 {
|
||||||
|
t.Errorf("header.KeyRotDelta = %d, want 3", header.KeyRotDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.PacketNum != 0 {
|
||||||
|
t.Errorf("header.PacketNum = %d, want 0", header.PacketNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify encrypted data was written
|
||||||
|
encryptedData := written[CryptPacketHeaderLength:]
|
||||||
|
if len(encryptedData) != int(header.DataSize) {
|
||||||
|
t.Errorf("encrypted data length = %d, want %d", len(encryptedData), header.DataSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_SendPacket_MultiplePackets(t *testing.T) {
|
||||||
|
mockConn := newMockConn(nil)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
// Send first packet
|
||||||
|
err := cc.SendPacket([]byte{0x01, 0x02})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendPacket(1) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.sentPackets != 1 {
|
||||||
|
t.Errorf("After 1 packet: sentPackets = %d, want 1", cc.sentPackets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send second packet
|
||||||
|
err = cc.SendPacket([]byte{0x03, 0x04})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendPacket(2) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.sentPackets != 2 {
|
||||||
|
t.Errorf("After 2 packets: sentPackets = %d, want 2", cc.sentPackets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send third packet
|
||||||
|
err = cc.SendPacket([]byte{0x05, 0x06})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendPacket(3) error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.sentPackets != 3 {
|
||||||
|
t.Errorf("After 3 packets: sentPackets = %d, want 3", cc.sentPackets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_SendPacket_KeyRotation(t *testing.T) {
|
||||||
|
mockConn := newMockConn(nil)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
initialKey := cc.sendKeyRot
|
||||||
|
|
||||||
|
err := cc.SendPacket([]byte{0x01, 0x02, 0x03})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendPacket() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key should have been rotated (keyRotDelta=3, so new key = 3 * (oldKey + 1))
|
||||||
|
expectedKey := 3 * (initialKey + 1)
|
||||||
|
if cc.sendKeyRot != expectedKey {
|
||||||
|
t.Errorf("sendKeyRot = %d, want %d", cc.sendKeyRot, expectedKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_SendPacket_WriteError(t *testing.T) {
|
||||||
|
mockConn := newMockConn(nil)
|
||||||
|
mockConn.writeErr = errors.New("write error")
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
err := cc.SendPacket([]byte{0x01, 0x02, 0x03})
|
||||||
|
// Note: Current implementation doesn't return write error
|
||||||
|
// This test documents the behavior
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("SendPacket() returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_Success(t *testing.T) {
|
||||||
|
// Save original config and restore after test
|
||||||
|
originalMode := _config.ErupeConfig.RealClientMode
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z1 // Use older mode for simpler test
|
||||||
|
defer func() {
|
||||||
|
_config.ErupeConfig.RealClientMode = originalMode
|
||||||
|
}()
|
||||||
|
|
||||||
|
testData := []byte{0x74, 0x65, 0x73, 0x74} // "test"
|
||||||
|
key := uint32(0)
|
||||||
|
|
||||||
|
// Encrypt the data
|
||||||
|
encryptedData, combinedCheck, check0, check1, check2 := crypto.Crypto(testData, key, true, nil)
|
||||||
|
|
||||||
|
// Build header
|
||||||
|
header := &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: uint16(len(encryptedData)),
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: check0,
|
||||||
|
Check1: check1,
|
||||||
|
Check2: check2,
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes, _ := header.Encode()
|
||||||
|
|
||||||
|
// Combine header and encrypted data
|
||||||
|
packet := append(headerBytes, encryptedData...)
|
||||||
|
|
||||||
|
mockConn := newMockConn(packet)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
// Set the key to match what we used for encryption
|
||||||
|
cc.readKeyRot = key
|
||||||
|
|
||||||
|
result, err := cc.ReadPacket()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadPacket() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(result, testData) {
|
||||||
|
t.Errorf("ReadPacket() = %v, want %v", result, testData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.prevRecvPacketCombinedCheck != combinedCheck {
|
||||||
|
t.Errorf("prevRecvPacketCombinedCheck = %d, want %d", cc.prevRecvPacketCombinedCheck, combinedCheck)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_KeyRotation(t *testing.T) {
|
||||||
|
// Save original config and restore after test
|
||||||
|
originalMode := _config.ErupeConfig.RealClientMode
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z1
|
||||||
|
defer func() {
|
||||||
|
_config.ErupeConfig.RealClientMode = originalMode
|
||||||
|
}()
|
||||||
|
|
||||||
|
testData := []byte{0x01, 0x02, 0x03, 0x04}
|
||||||
|
key := uint32(995117)
|
||||||
|
keyRotDelta := byte(3)
|
||||||
|
|
||||||
|
// Calculate expected rotated key
|
||||||
|
rotatedKey := uint32(keyRotDelta) * (key + 1)
|
||||||
|
|
||||||
|
// Encrypt with the rotated key
|
||||||
|
encryptedData, _, check0, check1, check2 := crypto.Crypto(testData, rotatedKey, true, nil)
|
||||||
|
|
||||||
|
// Build header with key rotation
|
||||||
|
header := &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: keyRotDelta,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: uint16(len(encryptedData)),
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: check0,
|
||||||
|
Check1: check1,
|
||||||
|
Check2: check2,
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes, _ := header.Encode()
|
||||||
|
packet := append(headerBytes, encryptedData...)
|
||||||
|
|
||||||
|
mockConn := newMockConn(packet)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
cc.readKeyRot = key
|
||||||
|
|
||||||
|
result, err := cc.ReadPacket()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadPacket() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(result, testData) {
|
||||||
|
t.Errorf("ReadPacket() = %v, want %v", result, testData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify key was rotated
|
||||||
|
if cc.readKeyRot != rotatedKey {
|
||||||
|
t.Errorf("readKeyRot = %d, want %d", cc.readKeyRot, rotatedKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_NoKeyRotation(t *testing.T) {
|
||||||
|
// Save original config and restore after test
|
||||||
|
originalMode := _config.ErupeConfig.RealClientMode
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z1
|
||||||
|
defer func() {
|
||||||
|
_config.ErupeConfig.RealClientMode = originalMode
|
||||||
|
}()
|
||||||
|
|
||||||
|
testData := []byte{0x01, 0x02}
|
||||||
|
key := uint32(12345)
|
||||||
|
|
||||||
|
// Encrypt without key rotation
|
||||||
|
encryptedData, _, check0, check1, check2 := crypto.Crypto(testData, key, true, nil)
|
||||||
|
|
||||||
|
header := &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0, // No rotation
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: uint16(len(encryptedData)),
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: check0,
|
||||||
|
Check1: check1,
|
||||||
|
Check2: check2,
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes, _ := header.Encode()
|
||||||
|
packet := append(headerBytes, encryptedData...)
|
||||||
|
|
||||||
|
mockConn := newMockConn(packet)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
cc.readKeyRot = key
|
||||||
|
|
||||||
|
originalKeyRot := cc.readKeyRot
|
||||||
|
|
||||||
|
result, err := cc.ReadPacket()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadPacket() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(result, testData) {
|
||||||
|
t.Errorf("ReadPacket() = %v, want %v", result, testData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify key was NOT rotated
|
||||||
|
if cc.readKeyRot != originalKeyRot {
|
||||||
|
t.Errorf("readKeyRot = %d, want %d (should not have changed)", cc.readKeyRot, originalKeyRot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_HeaderReadError(t *testing.T) {
|
||||||
|
mockConn := newMockConn([]byte{0x01, 0x02}) // Only 2 bytes, header needs 14
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
_, err := cc.ReadPacket()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ReadPacket() error = nil, want error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
t.Errorf("ReadPacket() error = %v, want io.EOF or io.ErrUnexpectedEOF", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_InvalidHeader(t *testing.T) {
|
||||||
|
// Create invalid header data (wrong endianness or malformed)
|
||||||
|
invalidHeader := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
|
||||||
|
mockConn := newMockConn(invalidHeader)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
_, err := cc.ReadPacket()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ReadPacket() error = nil, want error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_BodyReadError(t *testing.T) {
|
||||||
|
// Save original config and restore after test
|
||||||
|
originalMode := _config.ErupeConfig.RealClientMode
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z1
|
||||||
|
defer func() {
|
||||||
|
_config.ErupeConfig.RealClientMode = originalMode
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create valid header but incomplete body
|
||||||
|
header := &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: 100, // Claim 100 bytes
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0x1234,
|
||||||
|
Check1: 0x5678,
|
||||||
|
Check2: 0x9ABC,
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes, _ := header.Encode()
|
||||||
|
incompleteBody := []byte{0x01, 0x02, 0x03} // Only 3 bytes, not 100
|
||||||
|
|
||||||
|
packet := append(headerBytes, incompleteBody...)
|
||||||
|
|
||||||
|
mockConn := newMockConn(packet)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
|
||||||
|
_, err := cc.ReadPacket()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ReadPacket() error = nil, want error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_ReadPacket_ChecksumMismatch(t *testing.T) {
|
||||||
|
// Save original config and restore after test
|
||||||
|
originalMode := _config.ErupeConfig.RealClientMode
|
||||||
|
_config.ErupeConfig.RealClientMode = _config.Z1
|
||||||
|
defer func() {
|
||||||
|
_config.ErupeConfig.RealClientMode = originalMode
|
||||||
|
}()
|
||||||
|
|
||||||
|
testData := []byte{0x01, 0x02, 0x03, 0x04}
|
||||||
|
key := uint32(0)
|
||||||
|
|
||||||
|
encryptedData, _, _, _, _ := crypto.Crypto(testData, key, true, nil)
|
||||||
|
|
||||||
|
// Build header with WRONG checksums
|
||||||
|
header := &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: uint16(len(encryptedData)),
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0xFFFF, // Wrong checksum
|
||||||
|
Check1: 0xFFFF, // Wrong checksum
|
||||||
|
Check2: 0xFFFF, // Wrong checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes, _ := header.Encode()
|
||||||
|
packet := append(headerBytes, encryptedData...)
|
||||||
|
|
||||||
|
mockConn := newMockConn(packet)
|
||||||
|
cc := NewCryptConn(mockConn)
|
||||||
|
cc.readKeyRot = key
|
||||||
|
|
||||||
|
_, err := cc.ReadPacket()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("ReadPacket() error = nil, want error for checksum mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedErr := "decrypted data checksum doesn't match header"
|
||||||
|
if err.Error() != expectedErr {
|
||||||
|
t.Errorf("ReadPacket() error = %q, want %q", err.Error(), expectedErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptConn_Interface(t *testing.T) {
|
||||||
|
// Test that CryptConn implements Conn interface
|
||||||
|
var _ Conn = (*CryptConn)(nil)
|
||||||
|
}
|
||||||
385
network/crypt_packet_test.go
Normal file
385
network/crypt_packet_test.go
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCryptPacketHeader_ValidData(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
expected *CryptPacketHeader
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic header",
|
||||||
|
data: []byte{
|
||||||
|
0x03, // Pf0
|
||||||
|
0x03, // KeyRotDelta
|
||||||
|
0x00, 0x01, // PacketNum (1)
|
||||||
|
0x00, 0x0A, // DataSize (10)
|
||||||
|
0x00, 0x00, // PrevPacketCombinedCheck (0)
|
||||||
|
0x12, 0x34, // Check0 (0x1234)
|
||||||
|
0x56, 0x78, // Check1 (0x5678)
|
||||||
|
0x9A, 0xBC, // Check2 (0x9ABC)
|
||||||
|
},
|
||||||
|
expected: &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0x03,
|
||||||
|
PacketNum: 1,
|
||||||
|
DataSize: 10,
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0x1234,
|
||||||
|
Check1: 0x5678,
|
||||||
|
Check2: 0x9ABC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all zero values",
|
||||||
|
data: []byte{
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
},
|
||||||
|
expected: &CryptPacketHeader{
|
||||||
|
Pf0: 0x00,
|
||||||
|
KeyRotDelta: 0x00,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: 0,
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0,
|
||||||
|
Check1: 0,
|
||||||
|
Check2: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max values",
|
||||||
|
data: []byte{
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
},
|
||||||
|
expected: &CryptPacketHeader{
|
||||||
|
Pf0: 0xFF,
|
||||||
|
KeyRotDelta: 0xFF,
|
||||||
|
PacketNum: 0xFFFF,
|
||||||
|
DataSize: 0xFFFF,
|
||||||
|
PrevPacketCombinedCheck: 0xFFFF,
|
||||||
|
Check0: 0xFFFF,
|
||||||
|
Check1: 0xFFFF,
|
||||||
|
Check2: 0xFFFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := NewCryptPacketHeader(tt.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCryptPacketHeader() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Pf0 != tt.expected.Pf0 {
|
||||||
|
t.Errorf("Pf0 = 0x%X, want 0x%X", result.Pf0, tt.expected.Pf0)
|
||||||
|
}
|
||||||
|
if result.KeyRotDelta != tt.expected.KeyRotDelta {
|
||||||
|
t.Errorf("KeyRotDelta = 0x%X, want 0x%X", result.KeyRotDelta, tt.expected.KeyRotDelta)
|
||||||
|
}
|
||||||
|
if result.PacketNum != tt.expected.PacketNum {
|
||||||
|
t.Errorf("PacketNum = 0x%X, want 0x%X", result.PacketNum, tt.expected.PacketNum)
|
||||||
|
}
|
||||||
|
if result.DataSize != tt.expected.DataSize {
|
||||||
|
t.Errorf("DataSize = 0x%X, want 0x%X", result.DataSize, tt.expected.DataSize)
|
||||||
|
}
|
||||||
|
if result.PrevPacketCombinedCheck != tt.expected.PrevPacketCombinedCheck {
|
||||||
|
t.Errorf("PrevPacketCombinedCheck = 0x%X, want 0x%X", result.PrevPacketCombinedCheck, tt.expected.PrevPacketCombinedCheck)
|
||||||
|
}
|
||||||
|
if result.Check0 != tt.expected.Check0 {
|
||||||
|
t.Errorf("Check0 = 0x%X, want 0x%X", result.Check0, tt.expected.Check0)
|
||||||
|
}
|
||||||
|
if result.Check1 != tt.expected.Check1 {
|
||||||
|
t.Errorf("Check1 = 0x%X, want 0x%X", result.Check1, tt.expected.Check1)
|
||||||
|
}
|
||||||
|
if result.Check2 != tt.expected.Check2 {
|
||||||
|
t.Errorf("Check2 = 0x%X, want 0x%X", result.Check2, tt.expected.Check2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCryptPacketHeader_InvalidData(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty data",
|
||||||
|
data: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too short - 1 byte",
|
||||||
|
data: []byte{0x03},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too short - 13 bytes",
|
||||||
|
data: []byte{0x03, 0x03, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9A},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too short - 7 bytes",
|
||||||
|
data: []byte{0x03, 0x03, 0x00, 0x01, 0x00, 0x0A, 0x00},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := NewCryptPacketHeader(tt.data)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("NewCryptPacketHeader() error = nil, want error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCryptPacketHeader_ExtraDataIgnored(t *testing.T) {
|
||||||
|
// Test that extra data beyond 14 bytes is ignored
|
||||||
|
data := []byte{
|
||||||
|
0x03, 0x03,
|
||||||
|
0x00, 0x01,
|
||||||
|
0x00, 0x0A,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x12, 0x34,
|
||||||
|
0x56, 0x78,
|
||||||
|
0x9A, 0xBC,
|
||||||
|
0xFF, 0xFF, 0xFF, // Extra bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := NewCryptPacketHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCryptPacketHeader() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0x03,
|
||||||
|
PacketNum: 1,
|
||||||
|
DataSize: 10,
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0x1234,
|
||||||
|
Check1: 0x5678,
|
||||||
|
Check2: 0x9ABC,
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Pf0 != expected.Pf0 || result.KeyRotDelta != expected.KeyRotDelta ||
|
||||||
|
result.PacketNum != expected.PacketNum || result.DataSize != expected.DataSize {
|
||||||
|
t.Errorf("Extra data affected parsing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptPacketHeader_Encode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
header *CryptPacketHeader
|
||||||
|
expected []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic header",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0x03,
|
||||||
|
PacketNum: 1,
|
||||||
|
DataSize: 10,
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0x1234,
|
||||||
|
Check1: 0x5678,
|
||||||
|
Check2: 0x9ABC,
|
||||||
|
},
|
||||||
|
expected: []byte{
|
||||||
|
0x03, 0x03,
|
||||||
|
0x00, 0x01,
|
||||||
|
0x00, 0x0A,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x12, 0x34,
|
||||||
|
0x56, 0x78,
|
||||||
|
0x9A, 0xBC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all zeros",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0x00,
|
||||||
|
KeyRotDelta: 0x00,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: 0,
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0,
|
||||||
|
Check1: 0,
|
||||||
|
Check2: 0,
|
||||||
|
},
|
||||||
|
expected: []byte{
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max values",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0xFF,
|
||||||
|
KeyRotDelta: 0xFF,
|
||||||
|
PacketNum: 0xFFFF,
|
||||||
|
DataSize: 0xFFFF,
|
||||||
|
PrevPacketCombinedCheck: 0xFFFF,
|
||||||
|
Check0: 0xFFFF,
|
||||||
|
Check1: 0xFFFF,
|
||||||
|
Check2: 0xFFFF,
|
||||||
|
},
|
||||||
|
expected: []byte{
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.header.Encode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encode() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(result, tt.expected) {
|
||||||
|
t.Errorf("Encode() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the length is always 14
|
||||||
|
if len(result) != CryptPacketHeaderLength {
|
||||||
|
t.Errorf("Encode() length = %d, want %d", len(result), CryptPacketHeaderLength)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptPacketHeader_RoundTrip(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
header *CryptPacketHeader
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic header",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0x03,
|
||||||
|
KeyRotDelta: 0x03,
|
||||||
|
PacketNum: 100,
|
||||||
|
DataSize: 1024,
|
||||||
|
PrevPacketCombinedCheck: 0x1234,
|
||||||
|
Check0: 0xABCD,
|
||||||
|
Check1: 0xEF01,
|
||||||
|
Check2: 0x2345,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero values",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0x00,
|
||||||
|
KeyRotDelta: 0x00,
|
||||||
|
PacketNum: 0,
|
||||||
|
DataSize: 0,
|
||||||
|
PrevPacketCombinedCheck: 0,
|
||||||
|
Check0: 0,
|
||||||
|
Check1: 0,
|
||||||
|
Check2: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "max values",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0xFF,
|
||||||
|
KeyRotDelta: 0xFF,
|
||||||
|
PacketNum: 0xFFFF,
|
||||||
|
DataSize: 0xFFFF,
|
||||||
|
PrevPacketCombinedCheck: 0xFFFF,
|
||||||
|
Check0: 0xFFFF,
|
||||||
|
Check1: 0xFFFF,
|
||||||
|
Check2: 0xFFFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "realistic values",
|
||||||
|
header: &CryptPacketHeader{
|
||||||
|
Pf0: 0x07,
|
||||||
|
KeyRotDelta: 0x03,
|
||||||
|
PacketNum: 523,
|
||||||
|
DataSize: 2048,
|
||||||
|
PrevPacketCombinedCheck: 0x2A56,
|
||||||
|
Check0: 0x06EA,
|
||||||
|
Check1: 0x0215,
|
||||||
|
Check2: 0x8FB3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Encode
|
||||||
|
encoded, err := tt.header.Encode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Encode() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
decoded, err := NewCryptPacketHeader(encoded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewCryptPacketHeader() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
if decoded.Pf0 != tt.header.Pf0 {
|
||||||
|
t.Errorf("Pf0 = 0x%X, want 0x%X", decoded.Pf0, tt.header.Pf0)
|
||||||
|
}
|
||||||
|
if decoded.KeyRotDelta != tt.header.KeyRotDelta {
|
||||||
|
t.Errorf("KeyRotDelta = 0x%X, want 0x%X", decoded.KeyRotDelta, tt.header.KeyRotDelta)
|
||||||
|
}
|
||||||
|
if decoded.PacketNum != tt.header.PacketNum {
|
||||||
|
t.Errorf("PacketNum = 0x%X, want 0x%X", decoded.PacketNum, tt.header.PacketNum)
|
||||||
|
}
|
||||||
|
if decoded.DataSize != tt.header.DataSize {
|
||||||
|
t.Errorf("DataSize = 0x%X, want 0x%X", decoded.DataSize, tt.header.DataSize)
|
||||||
|
}
|
||||||
|
if decoded.PrevPacketCombinedCheck != tt.header.PrevPacketCombinedCheck {
|
||||||
|
t.Errorf("PrevPacketCombinedCheck = 0x%X, want 0x%X", decoded.PrevPacketCombinedCheck, tt.header.PrevPacketCombinedCheck)
|
||||||
|
}
|
||||||
|
if decoded.Check0 != tt.header.Check0 {
|
||||||
|
t.Errorf("Check0 = 0x%X, want 0x%X", decoded.Check0, tt.header.Check0)
|
||||||
|
}
|
||||||
|
if decoded.Check1 != tt.header.Check1 {
|
||||||
|
t.Errorf("Check1 = 0x%X, want 0x%X", decoded.Check1, tt.header.Check1)
|
||||||
|
}
|
||||||
|
if decoded.Check2 != tt.header.Check2 {
|
||||||
|
t.Errorf("Check2 = 0x%X, want 0x%X", decoded.Check2, tt.header.Check2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptPacketHeaderLength_Constant(t *testing.T) {
|
||||||
|
if CryptPacketHeaderLength != 14 {
|
||||||
|
t.Errorf("CryptPacketHeaderLength = %d, want 14", CryptPacketHeaderLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user