fix(battle-node): clip SIO ack arg instead of checked-cast throwing on overflow

This commit is contained in:
gamer147
2026-06-01 11:13:24 -04:00
parent 453865ade2
commit 4dd61343aa
2 changed files with 71 additions and 2 deletions

View File

@@ -342,10 +342,35 @@ public sealed class BattleSession
}
}
/// <summary>
/// Clip a long ack arg into the int range Socket.IO v2's typed AckResponse API accepts.
/// Logs a warning on clip; the implausibly-large pubSeq case is observationally
/// indistinguishable at the client (BestHTTP.SocketIO discards the echoed value), so
/// clipping is safer than the prior <c>checked((int)arg)</c> that threw and killed the
/// session on overflow.
/// </summary>
internal static int ClipAckArg(long arg, ILogger log, string battleId)
{
if (arg > int.MaxValue)
{
log.LogWarning(
"BattleSession {Bid}: pubSeq {Seq} exceeds int.MaxValue; clipping ack arg.",
battleId, arg);
return int.MaxValue;
}
if (arg < int.MinValue)
{
log.LogWarning(
"BattleSession {Bid}: pubSeq {Seq} below int.MinValue; clipping ack arg.",
battleId, arg);
return int.MinValue;
}
return (int)arg;
}
private async Task SendSioAckAsync(int ackId, long arg)
{
// checked() ensures we'd notice if pubSeq ever exceeded int.MaxValue (not realistic but defensive).
var ack = SocketIoFrame.AckResponse(ackId, checked((int)arg));
var ack = SocketIoFrame.AckResponse(ackId, ClipAckArg(arg, _log, BattleId));
var (text, _) = ack.Encode();
var eioText = $"{(int)EngineIoPacketType.Message}{text}";
await SendTextAsync(eioText, _sessionCt);

View File

@@ -0,0 +1,44 @@
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
using SVSim.BattleNode.Sessions;
namespace SVSim.UnitTests.BattleNode.Sessions;
[TestFixture]
public class ClipAckArgTests
{
[Test]
public void InRange_ReturnsArgUnchanged()
{
var result = BattleSession.ClipAckArg(42L, NullLogger.Instance, battleId: "b");
Assert.That(result, Is.EqualTo(42));
}
[Test]
public void AboveIntMax_ClipsToIntMaxValue()
{
var result = BattleSession.ClipAckArg((long)int.MaxValue + 1L, NullLogger.Instance, battleId: "b");
Assert.That(result, Is.EqualTo(int.MaxValue));
}
[Test]
public void BelowIntMin_ClipsToIntMinValue()
{
var result = BattleSession.ClipAckArg((long)int.MinValue - 1L, NullLogger.Instance, battleId: "b");
Assert.That(result, Is.EqualTo(int.MinValue));
}
[Test]
public void AtIntMaxBoundary_ReturnsIntMaxValue()
{
var result = BattleSession.ClipAckArg((long)int.MaxValue, NullLogger.Instance, battleId: "b");
Assert.That(result, Is.EqualTo(int.MaxValue));
}
[Test]
public void AtIntMinBoundary_ReturnsIntMinValue()
{
var result = BattleSession.ClipAckArg((long)int.MinValue, NullLogger.Instance, battleId: "b");
Assert.That(result, Is.EqualTo(int.MinValue));
}
}