fix(handlers): harden gacha_items and boost time limit ACK paths

Both bugs surfaced when running protbot against a live Erupe instance.

LoadColumnWithDefault now treats an empty bytea ('\x', len 0) the same
as NULL. The postgres driver returns a non-nil empty slice for empty
bytea, so the prior `data == nil` check let a zero-byte slice reach
handleMsgMhfReceiveGachaItem, which forwarded it to the client as a
malformed gacha_items response. The MHF client interprets a zero-byte
count field as a protocol error and crashes the gacha menu, matching
the #175 symptom class. The handler also gains a defensive fallback
for len(data) == 0 in case another caller hits the same edge.

handleMsgMhfGetBoostTimeLimit was sending two ACKs for a single
request: doAckBufSucceed with the real payload, then an unconditional
doAckSimpleSucceed on the same ack handle. The second ACK was dead on
arrival (the handle was already consumed) but is a latent protocol
bug. Drop it and update the regression test that was asserting the
buggy 2-packet behavior.
This commit is contained in:
Houmgaor
2026-04-06 18:04:10 +02:00
parent 3e9f3d1b62
commit 8b2667f7a0
5 changed files with 34 additions and 6 deletions

View File

@@ -392,6 +392,27 @@ func TestLoadColumnWithDefaultExistingData(t *testing.T) {
}
}
// TestLoadColumnWithDefaultEmptyBytea verifies that an empty bytea ('\x', len 0)
// is treated the same as NULL and returns the default value. Without this, the
// postgres driver returns a non-nil empty slice that would reach the client
// as a zero-byte ACK payload and crash the MHF gacha menu (see #175).
func TestLoadColumnWithDefaultEmptyBytea(t *testing.T) {
repo, db, charID := setupCharRepo(t)
if _, err := db.Exec("UPDATE characters SET skin_hist=''::bytea WHERE id=$1", charID); err != nil {
t.Fatalf("Setup failed: %v", err)
}
defaultVal := []byte{0x00, 0x01, 0x02}
got, err := repo.LoadColumnWithDefault(charID, "skin_hist", defaultVal)
if err != nil {
t.Fatalf("LoadColumnWithDefault failed: %v", err)
}
if len(got) != 3 || got[0] != 0x00 || got[2] != 0x02 {
t.Errorf("Expected default value for empty bytea, got: %x", got)
}
}
func TestSetDeleted(t *testing.T) {
repo, db, charID := setupCharRepo(t)