服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
332 lines
12 KiB
Go
332 lines
12 KiB
Go
package dnfbridge
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCurrentPartyUDPRelayDatagramTraceIsBounded(t *testing.T) {
|
|
for count := uint64(0); count <= 1025; count++ {
|
|
want := count >= 1 && count <= 8 || count >= 16 && count&(count-1) == 0
|
|
if got := currentPartyUDPRelayTraceDatagram(count); got != want {
|
|
t.Fatalf("trace count %d=%t want %t", count, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCurrentPartyUDPRelayForwardsOnlyAuthenticatedDirectedLegs(t *testing.T) {
|
|
portStart := testCurrentPartyUDPRelayPortStart(t)
|
|
relay, err := newCurrentPartyUDPRelay(true, "127.0.0.1", portStart, testCurrentPartyUDPRelayPortSpan, nil)
|
|
if err != nil {
|
|
t.Fatalf("new UDP relay: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := relay.Close(); err != nil {
|
|
t.Fatalf("close UDP relay: %v", err)
|
|
}
|
|
}()
|
|
|
|
bindings := []currentPartyUDPRelayBinding{
|
|
{characterID: 101, generation: 1, address: [4]byte{127, 0, 0, 1}},
|
|
{characterID: 202, generation: 1, address: [4]byte{127, 0, 0, 1}},
|
|
}
|
|
if !relay.Sync(77, bindings) {
|
|
t.Fatal("relay did not create a complete two-member room")
|
|
}
|
|
addressAB, portAB, ok := relay.Endpoint(77, 101, 202)
|
|
if !ok {
|
|
t.Fatal("missing A-to-B relay endpoint")
|
|
}
|
|
addressBA, portBA, ok := relay.Endpoint(77, 202, 101)
|
|
if !ok || portAB == portBA {
|
|
t.Fatalf("invalid directed relay endpoints: AB=%d BA=%d ok=%v", portAB, portBA, ok)
|
|
}
|
|
|
|
clientA := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientA.Close()
|
|
clientB := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientB.Close()
|
|
wrongSource := testCurrentPartyUDPClient(t, "127.0.0.2", portStart)
|
|
defer wrongSource.Close()
|
|
|
|
endpointAB := &net.UDPAddr{IP: net.IPv4(addressAB[0], addressAB[1], addressAB[2], addressAB[3]), Port: int(portAB)}
|
|
endpointBA := &net.UDPAddr{IP: net.IPv4(addressBA[0], addressBA[1], addressBA[2], addressBA[3]), Port: int(portBA)}
|
|
if _, err := wrongSource.WriteToUDP([]byte("forged"), endpointAB); err != nil {
|
|
t.Fatalf("send forged endpoint registration: %v", err)
|
|
}
|
|
if _, err := clientB.WriteToUDP([]byte("b-ready"), endpointBA); err != nil {
|
|
t.Fatalf("send B handshake: %v", err)
|
|
}
|
|
if _, err := clientA.WriteToUDP([]byte("A-payload"), endpointAB); err != nil {
|
|
t.Fatalf("send A payload: %v", err)
|
|
}
|
|
|
|
// B's handshake and A's payload travel on separate sockets, and UDP gives
|
|
// no cross-socket ordering: on a loaded machine the payload can reach the
|
|
// relay before the handshake registers B's leg, and the relay rightly
|
|
// drops it. Resending the payload until it arrives keeps the assertion
|
|
// about WHAT is forwarded while tolerating WHEN registration wins.
|
|
buffer := make([]byte, 128)
|
|
var n int
|
|
overall := time.Now().Add(5 * time.Second)
|
|
for {
|
|
if err := clientB.SetReadDeadline(time.Now().Add(200 * time.Millisecond)); err != nil {
|
|
t.Fatalf("set client B deadline: %v", err)
|
|
}
|
|
var err error
|
|
n, _, err = clientB.ReadFromUDP(buffer)
|
|
if err == nil {
|
|
break
|
|
}
|
|
if !errors.Is(err, os.ErrDeadlineExceeded) || time.Now().After(overall) {
|
|
t.Fatalf("receive relayed payload: %v", err)
|
|
}
|
|
if _, err := clientA.WriteToUDP([]byte("A-payload"), endpointAB); err != nil {
|
|
t.Fatalf("resend A payload: %v", err)
|
|
}
|
|
}
|
|
if !bytes.Equal(buffer[:n], []byte("A-payload")) {
|
|
t.Fatalf("relayed payload = %q, want A-payload", buffer[:n])
|
|
}
|
|
}
|
|
|
|
func TestCurrentPartyUDPRelayForwardsFirstHandshakeToRegisteredPeerPort(t *testing.T) {
|
|
portStart := testCurrentPartyUDPRelayPortStart(t)
|
|
relay, err := newCurrentPartyUDPRelay(true, "127.0.0.1", portStart, testCurrentPartyUDPRelayPortSpan, nil)
|
|
if err != nil {
|
|
t.Fatalf("new UDP relay: %v", err)
|
|
}
|
|
defer relay.Close()
|
|
|
|
clientA := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientA.Close()
|
|
clientB := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientB.Close()
|
|
bindings := []currentPartyUDPRelayBinding{
|
|
{characterID: 101, generation: 1, address: [4]byte{127, 0, 0, 1}, port: uint16(clientA.LocalAddr().(*net.UDPAddr).Port)},
|
|
{characterID: 202, generation: 1, address: [4]byte{127, 0, 0, 1}, port: uint16(clientB.LocalAddr().(*net.UDPAddr).Port)},
|
|
}
|
|
if !relay.Sync(78, bindings) {
|
|
t.Fatal("relay did not create registered two-member room")
|
|
}
|
|
addressAB, portAB, ok := relay.Endpoint(78, 101, 202)
|
|
if !ok {
|
|
t.Fatal("missing A-to-B relay endpoint")
|
|
}
|
|
endpointAB := &net.UDPAddr{IP: net.IPv4(addressAB[0], addressAB[1], addressAB[2], addressAB[3]), Port: int(portAB)}
|
|
if _, err := clientA.WriteToUDP([]byte("first-handshake"), endpointAB); err != nil {
|
|
t.Fatalf("send first handshake: %v", err)
|
|
}
|
|
|
|
buffer := make([]byte, 128)
|
|
if err := clientB.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
|
t.Fatalf("set client B deadline: %v", err)
|
|
}
|
|
n, _, err := clientB.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
t.Fatalf("receive first relayed handshake: %v", err)
|
|
}
|
|
if !bytes.Equal(buffer[:n], []byte("first-handshake")) {
|
|
t.Fatalf("first relayed handshake = %q", buffer[:n])
|
|
}
|
|
}
|
|
|
|
func TestCurrentPartyUDPRelayLearnsActivePartySocketAfterSeededProbePort(t *testing.T) {
|
|
portStart := testCurrentPartyUDPRelayPortStart(t)
|
|
relay, err := newCurrentPartyUDPRelay(true, "127.0.0.1", portStart, testCurrentPartyUDPRelayPortSpan, nil)
|
|
if err != nil {
|
|
t.Fatalf("new UDP relay: %v", err)
|
|
}
|
|
defer relay.Close()
|
|
|
|
probeA := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer probeA.Close()
|
|
partyA := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer partyA.Close()
|
|
clientB := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientB.Close()
|
|
bindings := []currentPartyUDPRelayBinding{
|
|
{characterID: 101, generation: 1, address: [4]byte{127, 0, 0, 1}, port: uint16(probeA.LocalAddr().(*net.UDPAddr).Port)},
|
|
{characterID: 202, generation: 1, address: [4]byte{127, 0, 0, 1}, port: uint16(clientB.LocalAddr().(*net.UDPAddr).Port)},
|
|
}
|
|
if !relay.Sync(79, bindings) {
|
|
t.Fatal("relay did not create seeded two-member room")
|
|
}
|
|
addressAB, portAB, ok := relay.Endpoint(79, 101, 202)
|
|
if !ok {
|
|
t.Fatal("missing A-to-B relay endpoint")
|
|
}
|
|
addressBA, portBA, ok := relay.Endpoint(79, 202, 101)
|
|
if !ok {
|
|
t.Fatal("missing B-to-A relay endpoint")
|
|
}
|
|
endpointAB := &net.UDPAddr{IP: net.IPv4(addressAB[0], addressAB[1], addressAB[2], addressAB[3]), Port: int(portAB)}
|
|
endpointBA := &net.UDPAddr{IP: net.IPv4(addressBA[0], addressBA[1], addressBA[2], addressBA[3]), Port: int(portBA)}
|
|
if _, err := partyA.WriteToUDP([]byte("A-live-source"), endpointAB); err != nil {
|
|
t.Fatalf("send A live-source handshake: %v", err)
|
|
}
|
|
buffer := make([]byte, 128)
|
|
if err := clientB.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
|
t.Fatalf("set B read deadline: %v", err)
|
|
}
|
|
if _, _, err := clientB.ReadFromUDP(buffer); err != nil {
|
|
t.Fatalf("receive A handshake at seeded B endpoint: %v", err)
|
|
}
|
|
if _, err := clientB.WriteToUDP([]byte("B-reply"), endpointBA); err != nil {
|
|
t.Fatalf("send B reply: %v", err)
|
|
}
|
|
if err := partyA.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
|
t.Fatalf("set A live socket deadline: %v", err)
|
|
}
|
|
n, _, err := partyA.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
t.Fatalf("receive B reply at learned A party socket: %v", err)
|
|
}
|
|
if !bytes.Equal(buffer[:n], []byte("B-reply")) {
|
|
t.Fatalf("learned A party socket payload=%q", buffer[:n])
|
|
}
|
|
if err := probeA.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
|
t.Fatalf("set A probe deadline: %v", err)
|
|
}
|
|
if _, _, err := probeA.ReadFromUDP(buffer); err == nil {
|
|
t.Fatal("B reply was still delivered to the inactive seeded probe socket")
|
|
}
|
|
}
|
|
|
|
func TestCurrentPartyUDPRelayPreservesLargeRealtimeActorDatagram(t *testing.T) {
|
|
portStart := testCurrentPartyUDPRelayPortStart(t)
|
|
relay, err := newCurrentPartyUDPRelay(true, "127.0.0.1", portStart, testCurrentPartyUDPRelayPortSpan, nil)
|
|
if err != nil {
|
|
t.Fatalf("new UDP relay: %v", err)
|
|
}
|
|
defer relay.Close()
|
|
|
|
clientA := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientA.Close()
|
|
clientB := testCurrentPartyUDPClient(t, "127.0.0.1", portStart)
|
|
defer clientB.Close()
|
|
bindings := []currentPartyUDPRelayBinding{
|
|
{characterID: 101, generation: 1, address: [4]byte{127, 0, 0, 1}, port: uint16(clientA.LocalAddr().(*net.UDPAddr).Port)},
|
|
{characterID: 202, generation: 1, address: [4]byte{127, 0, 0, 1}, port: uint16(clientB.LocalAddr().(*net.UDPAddr).Port)},
|
|
}
|
|
if !relay.Sync(80, bindings) {
|
|
t.Fatal("relay did not create large-datagram test room")
|
|
}
|
|
addressAB, portAB, ok := relay.Endpoint(80, 101, 202)
|
|
if !ok {
|
|
t.Fatal("missing A-to-B relay endpoint")
|
|
}
|
|
|
|
// Exceeds the former 2 KiB buffer and models one complete reliable actor
|
|
// snapshot containing riding/transform plus combat state.
|
|
payload := make([]byte, 32*1024)
|
|
for index := range payload {
|
|
payload[index] = byte(index*31 + 7)
|
|
}
|
|
endpointAB := &net.UDPAddr{
|
|
IP: net.IPv4(addressAB[0], addressAB[1], addressAB[2], addressAB[3]),
|
|
Port: int(portAB),
|
|
}
|
|
if _, err := clientA.WriteToUDP(payload, endpointAB); err != nil {
|
|
t.Fatalf("send large actor datagram: %v", err)
|
|
}
|
|
|
|
buffer := make([]byte, len(payload)+1)
|
|
if err := clientB.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
|
t.Fatalf("set client B deadline: %v", err)
|
|
}
|
|
n, _, err := clientB.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
t.Fatalf("receive large actor datagram: %v", err)
|
|
}
|
|
if n != len(payload) || !bytes.Equal(buffer[:n], payload) {
|
|
t.Fatalf("large actor datagram bytes=%d want=%d equal=%t", n, len(payload), bytes.Equal(buffer[:n], payload))
|
|
}
|
|
}
|
|
|
|
func TestCurrentPartyUDPRelaySyncRemovesFormerMemberLegs(t *testing.T) {
|
|
portStart := testCurrentPartyUDPRelayPortStart(t)
|
|
relay, err := newCurrentPartyUDPRelay(true, "127.0.0.1", portStart, testCurrentPartyUDPRelayPortSpan, nil)
|
|
if err != nil {
|
|
t.Fatalf("new UDP relay: %v", err)
|
|
}
|
|
defer relay.Close()
|
|
|
|
first := []currentPartyUDPRelayBinding{
|
|
{characterID: 101, generation: 1, address: [4]byte{127, 0, 0, 1}},
|
|
{characterID: 202, generation: 1, address: [4]byte{127, 0, 0, 1}},
|
|
{characterID: 303, generation: 1, address: [4]byte{127, 0, 0, 1}},
|
|
}
|
|
if !relay.Sync(77, first) {
|
|
t.Fatal("initial relay sync failed")
|
|
}
|
|
if _, _, ok := relay.Endpoint(77, 101, 303); !ok {
|
|
t.Fatal("initial member endpoint missing")
|
|
}
|
|
if !relay.Sync(77, first[:2]) {
|
|
t.Fatal("member removal relay sync failed")
|
|
}
|
|
if _, _, ok := relay.Endpoint(77, 101, 303); ok {
|
|
t.Fatal("former member endpoint remained routable")
|
|
}
|
|
if _, _, ok := relay.Endpoint(77, 101, 202); !ok {
|
|
t.Fatal("remaining pair endpoint was removed")
|
|
}
|
|
}
|
|
|
|
// testCurrentPartyUDPRelayPortSpan is how many ports the relay may bind its legs
|
|
// from in these tests. It is shared with testCurrentPartyUDPClient so that a test
|
|
// client socket can never land on one of the legs' port numbers.
|
|
const testCurrentPartyUDPRelayPortSpan = 12
|
|
|
|
func testCurrentPartyUDPRelayPortStart(t *testing.T) int {
|
|
t.Helper()
|
|
for attempt := 0; attempt < 20; attempt++ {
|
|
listener, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
|
if err != nil {
|
|
t.Fatalf("allocate test UDP port: %v", err)
|
|
}
|
|
port := listener.LocalAddr().(*net.UDPAddr).Port
|
|
_ = listener.Close()
|
|
if port <= 65523 {
|
|
return port
|
|
}
|
|
}
|
|
t.Fatal("could not allocate a usable twelve-port UDP test range")
|
|
return 0
|
|
}
|
|
|
|
// testCurrentPartyUDPClient listens on an ephemeral loopback port that is kept
|
|
// outside the relay's own port window.
|
|
//
|
|
// The relay binds its legs on the wildcard address (0.0.0.0:port, deliberately:
|
|
// see newCurrentPartyUDPRelay) while this helper binds a specific address
|
|
// (127.0.0.1:port). Windows lets both exist, and the specific bind wins delivery,
|
|
// so a client socket that happened to land on a leg's port number silently
|
|
// swallowed every datagram addressed to that leg -- the relay then looked like it
|
|
// had failed to forward when in fact nothing ever reached it. The window start is
|
|
// handed in by the caller so the two allocations can never overlap.
|
|
func testCurrentPartyUDPClient(t *testing.T, ip string, windowStart int) *net.UDPConn {
|
|
t.Helper()
|
|
for attempt := 0; attempt < 64; attempt++ {
|
|
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.ParseIP(ip), Port: 0})
|
|
if err != nil {
|
|
t.Fatalf("listen UDP client %s: %v", ip, err)
|
|
}
|
|
port := conn.LocalAddr().(*net.UDPAddr).Port
|
|
if port < windowStart || port >= windowStart+testCurrentPartyUDPRelayPortSpan {
|
|
return conn
|
|
}
|
|
// Give the port back: the relay is about to bind this same number on the
|
|
// wildcard address, and holding it here would shadow that leg.
|
|
_ = conn.Close()
|
|
}
|
|
t.Fatalf("could not place UDP client %s outside relay window %d..%d",
|
|
ip, windowStart, windowStart+testCurrentPartyUDPRelayPortSpan-1)
|
|
return nil
|
|
}
|