fix(server): prevent refinement crashes from missing private option messages
This commit is contained in:
@@ -351,9 +351,14 @@ func EquipmentWire(entry Equipment) []byte {
|
||||
for _, option := range entry.SubOption {
|
||||
base = wire.AppendBytes(base, 4, equipmentOptionWire(option))
|
||||
}
|
||||
// The native ResolvedPrivateOption getter dereferences PrivateOption before
|
||||
// checking IsValid. A present empty message selects its GameData fallback;
|
||||
// an omitted field leaves the protobuf object null and crashes the UI.
|
||||
var private []byte
|
||||
if entry.PrivateOption != nil {
|
||||
base = wire.AppendBytes(base, 5, equipmentOptionWire(*entry.PrivateOption))
|
||||
private = equipmentOptionWire(*entry.PrivateOption)
|
||||
}
|
||||
base = wire.AppendBytes(base, 5, private)
|
||||
for _, rank := range entry.Rank {
|
||||
base = wire.AppendVarint(base, 6, rank)
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ func TestEquipmentUpgradeAndSequenceUseGameDataCosts(t *testing.T) {
|
||||
if err != nil || !handled || code != 37 {
|
||||
t.Fatalf("single upgrade code=%d handled=%v err=%v", code, handled, err)
|
||||
}
|
||||
assertEquipmentPrivateMessage(t, response)
|
||||
encoded, found, err := wire.Bytes(response, 1)
|
||||
if err != nil || !found {
|
||||
t.Fatalf("single upgrade equipment: %v", err)
|
||||
@@ -60,6 +61,7 @@ func TestEquipmentUpgradeAndSequenceUseGameDataCosts(t *testing.T) {
|
||||
if err != nil || !handled || code != 176 {
|
||||
t.Fatalf("sequence upgrade code=%d handled=%v err=%v", code, handled, err)
|
||||
}
|
||||
assertEquipmentPrivateMessage(t, response)
|
||||
if result, _, _ := wire.Varint(response, 3); result != equipUpgradeStopMaxLevel {
|
||||
t.Fatalf("sequence result=%d", result)
|
||||
}
|
||||
@@ -125,6 +127,7 @@ func TestEquipmentSmeltingImprovesByTotalScoreAndReplaysWithoutSecondCharge(t *t
|
||||
if err != nil || !handled || code != 105 {
|
||||
t.Fatalf("smelting code=%d handled=%v err=%v", code, handled, err)
|
||||
}
|
||||
assertEquipmentPrivateMessage(t, response)
|
||||
if result, found, _ := wire.Varint(response, 3); found || result != 0 {
|
||||
t.Fatalf("successful smelting result=%d found=%v", result, found)
|
||||
}
|
||||
@@ -227,6 +230,7 @@ func TestEquipmentSequenceSmeltingRepeatsAndStopsAtTargetScore(t *testing.T) {
|
||||
if err != nil || !handled || code != 177 {
|
||||
t.Fatalf("sequence smelting code=%d handled=%v err=%v", code, handled, err)
|
||||
}
|
||||
assertEquipmentPrivateMessage(t, response)
|
||||
if result, _, _ := wire.Varint(response, 3); result != equipUpgradeStopTargetLevel {
|
||||
t.Fatalf("sequence smelting result=%d", result)
|
||||
}
|
||||
@@ -723,3 +727,45 @@ func TestEquipmentChangeReplacesOnlyMatchingGameDataSlot(t *testing.T) {
|
||||
t.Fatalf("changed equipment=%+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEquipmentPrivateMessage(t *testing.T, response []byte) {
|
||||
t.Helper()
|
||||
entry, ok, err := wire.Bytes(response, 1)
|
||||
if err != nil || !ok {
|
||||
t.Fatal("equipment entry absent", err)
|
||||
}
|
||||
base, ok, err := wire.Bytes(entry, 5)
|
||||
if err != nil || !ok {
|
||||
t.Fatal("equipment base absent", err)
|
||||
}
|
||||
private, ok, err := wire.Bytes(base, 5)
|
||||
if err != nil || !ok {
|
||||
t.Fatal("native PrivateOption reference would be null", err)
|
||||
}
|
||||
if len(private) != 0 {
|
||||
t.Fatal("ordinary equipment has unexpected private option")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquipmentWirePrivateObjectPresenceAndExplicitOption(t *testing.T) {
|
||||
for _, private := range []*EquipmentOption{nil, {GroupID: 301, ID: 2}} {
|
||||
entry := Equipment{ID: 943035, InvenIndex: 1, Rank: []uint64{1, 2, 3}, PrivateOption: private}
|
||||
base, _, _ := wire.Bytes(EquipmentWire(entry), 5)
|
||||
option, present, err := wire.Bytes(base, 5)
|
||||
if err != nil || !present {
|
||||
t.Fatal("PrivateOption was omitted", err)
|
||||
}
|
||||
group, _, _ := wire.Varint(option, 1)
|
||||
id, _, _ := wire.Varint(option, 2)
|
||||
if private == nil {
|
||||
if len(option) != 0 || group != 0 || id != 0 {
|
||||
t.Fatal("empty fallback object malformed")
|
||||
}
|
||||
} else if group != private.GroupID || id != private.ID {
|
||||
t.Fatal("explicit private option altered")
|
||||
}
|
||||
if entry.PrivateOption != private {
|
||||
t.Fatal("wire generation mutated ownership")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user