mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-28 02:23:37 +01:00
feat(protbot): add headless MHF protocol bot as cmd/protbot
Copy MHBridge into the Erupe module as cmd/protbot/ so it can be built, tested, and maintained alongside the server. The bot implements the full sign → entrance → channel login flow and supports lobby entry, chat, session setup, and quest enumeration. The conn/ package keeps its own Blowfish crypto primitives to avoid importing erupe-ce/config (which requires a config file at init).
This commit is contained in:
152
cmd/protbot/conn/crypt_conn_test.go
Normal file
152
cmd/protbot/conn/crypt_conn_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCryptConnRoundTrip verifies that encrypting and decrypting a packet
|
||||
// through a pair of CryptConn instances produces the original data.
|
||||
func TestCryptConnRoundTrip(t *testing.T) {
|
||||
// Create an in-process TCP pipe.
|
||||
server, client := net.Pipe()
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
|
||||
sender := NewCryptConn(client)
|
||||
receiver := NewCryptConn(server)
|
||||
|
||||
testCases := [][]byte{
|
||||
{0x00, 0x14, 0x00, 0x00, 0x00, 0x01}, // Minimal login-like packet
|
||||
{0xDE, 0xAD, 0xBE, 0xEF},
|
||||
make([]byte, 256), // Larger packet
|
||||
}
|
||||
|
||||
for i, original := range testCases {
|
||||
// Send in a goroutine to avoid blocking.
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- sender.SendPacket(original)
|
||||
}()
|
||||
|
||||
received, err := receiver.ReadPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("case %d: ReadPacket error: %v", i, err)
|
||||
}
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
t.Fatalf("case %d: SendPacket error: %v", i, err)
|
||||
}
|
||||
|
||||
if len(received) != len(original) {
|
||||
t.Fatalf("case %d: length mismatch: got %d, want %d", i, len(received), len(original))
|
||||
}
|
||||
for j := range original {
|
||||
if received[j] != original[j] {
|
||||
t.Fatalf("case %d: byte %d mismatch: got 0x%02X, want 0x%02X", i, j, received[j], original[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCryptPacketHeaderRoundTrip verifies header encode/decode.
|
||||
func TestCryptPacketHeaderRoundTrip(t *testing.T) {
|
||||
original := &CryptPacketHeader{
|
||||
Pf0: 0x03,
|
||||
KeyRotDelta: 0x03,
|
||||
PacketNum: 42,
|
||||
DataSize: 100,
|
||||
PrevPacketCombinedCheck: 0x1234,
|
||||
Check0: 0xAAAA,
|
||||
Check1: 0xBBBB,
|
||||
Check2: 0xCCCC,
|
||||
}
|
||||
|
||||
encoded, err := original.Encode()
|
||||
if err != nil {
|
||||
t.Fatalf("Encode error: %v", err)
|
||||
}
|
||||
|
||||
if len(encoded) != CryptPacketHeaderLength {
|
||||
t.Fatalf("encoded length: got %d, want %d", len(encoded), CryptPacketHeaderLength)
|
||||
}
|
||||
|
||||
decoded, err := NewCryptPacketHeader(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCryptPacketHeader error: %v", err)
|
||||
}
|
||||
|
||||
if *decoded != *original {
|
||||
t.Fatalf("header mismatch:\ngot %+v\nwant %+v", *decoded, *original)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiPacketSequence verifies that key rotation stays in sync across
|
||||
// multiple sequential packets.
|
||||
func TestMultiPacketSequence(t *testing.T) {
|
||||
server, client := net.Pipe()
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
|
||||
sender := NewCryptConn(client)
|
||||
receiver := NewCryptConn(server)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
data := []byte{byte(i), byte(i + 1), byte(i + 2), byte(i + 3)}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- sender.SendPacket(data)
|
||||
}()
|
||||
|
||||
received, err := receiver.ReadPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("packet %d: ReadPacket error: %v", i, err)
|
||||
}
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
t.Fatalf("packet %d: SendPacket error: %v", i, err)
|
||||
}
|
||||
|
||||
for j := range data {
|
||||
if received[j] != data[j] {
|
||||
t.Fatalf("packet %d byte %d: got 0x%02X, want 0x%02X", i, j, received[j], data[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDialWithInit verifies that DialWithInit sends 8 NULL bytes on connect.
|
||||
func TestDialWithInit(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
done := make(chan []byte, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
buf := make([]byte, 8)
|
||||
_, _ = io.ReadFull(conn, buf)
|
||||
done <- buf
|
||||
}()
|
||||
|
||||
c, err := DialWithInit(listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
initBytes := <-done
|
||||
for i, b := range initBytes {
|
||||
if b != 0 {
|
||||
t.Fatalf("init byte %d: got 0x%02X, want 0x00", i, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user