fix(battle-node): harden SIO parse + narrow Matched OppoId/Seed to int

#3: SocketIoFrame.Parse now range-checks the packet type char (was
unchecked cast — any char outside 0-6 produced an undefined enum
value) and uses int.TryParse for ack-id (was int.Parse — a >10-digit
ack-id threw OverflowException, tearing down the WS mid-game). Both
now throw ArgumentException consistently. The read loop in
RealParticipant wraps both EIO and SIO parse calls with try-catch so
a malformed frame is logged and skipped instead of killing the battle.

#4: MatchedSelfInfo/MatchedOppoInfo OppoId and Seed narrowed from
long to int. The client reads both with Convert.ToInt32 inside a
swallowing try/catch — any value > int.MaxValue silently dropped the
Matched event, preventing the battle from starting. Seed was already
int-range (BattleSeeds.Stable returns int); OppoId (viewer ID) is
~847M in captures, well under int.MaxValue. The narrowing cast now
happens explicitly in ServerBattleFrames.BuildMatched at the wire
boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-04 21:57:29 -04:00
parent e9af7af1b8
commit 99129c786c
9 changed files with 65 additions and 33 deletions

View File

@@ -125,4 +125,17 @@ public class SocketIoFrameTests
// The event name must be JSON-escaped: each " becomes \", and the literal \ becomes \\.
Assert.That(text, Does.Contain("\"weird \\\"name\\\" with \\\\ backslash\""));
}
[Test]
public void Parse_InvalidTypeChar_Throws()
{
var ex = Assert.Throws<ArgumentException>(() => SocketIoFrame.Parse("9[\"msg\"]"));
Assert.That(ex!.Message, Does.Contain("Invalid SIO type char"));
}
[Test]
public void Parse_OverflowingAckId_Throws()
{
Assert.Throws<ArgumentException>(() => SocketIoFrame.Parse("2999999999999[\"msg\"]"));
}
}