refactor(battle-node): WireConstants for SIO event names + crypto RNG battle id

This commit is contained in:
gamer147
2026-06-01 11:53:01 -04:00
parent 133346e3e8
commit ef3d7bb82b
4 changed files with 45 additions and 12 deletions

View File

@@ -1,3 +1,4 @@
using System.Security.Cryptography;
using SVSim.BattleNode.Sessions; using SVSim.BattleNode.Sessions;
namespace SVSim.BattleNode.Bridge; namespace SVSim.BattleNode.Bridge;
@@ -23,8 +24,13 @@ public sealed class MatchingBridge : IMatchingBridge
public PendingMatch RegisterPendingBattle(long viewerId) public PendingMatch RegisterPendingBattle(long viewerId)
{ {
// 12-digit decimal battle id mirrors the captures (e.g. "975695075012"). // 12-digit decimal battle id mirrors the captures (e.g. "975695075012").
// Cast to long before Math.Abs to avoid OverflowException on int.MinValue. // Two unbiased 6-digit draws concatenated — RandomNumberGenerator.GetInt32 uses
var battleId = (Math.Abs((long)Guid.NewGuid().GetHashCode()) % 1_000_000_000_000L).ToString("D12"); // rejection sampling so the result is uniform on [0, 10^6). The previous
// implementation (Guid.GetHashCode + mod) collapsed 128 bits into ~32 of entropy
// and tripped Math.Abs(int.MinValue) UB on the unlucky hash.
var hi = RandomNumberGenerator.GetInt32(0, 1_000_000);
var lo = RandomNumberGenerator.GetInt32(0, 1_000_000);
var battleId = $"{hi:D6}{lo:D6}";
_store.RegisterPending(new PendingBattle(battleId, viewerId)); _store.RegisterPending(new PendingBattle(battleId, viewerId));
return new PendingMatch(battleId, _options.NodeServerUrl); return new PendingMatch(battleId, _options.NodeServerUrl);
} }

View File

@@ -117,7 +117,7 @@ public static class ScriptedLifecycle
private static MsgEnvelope EnvelopeForPush(NetworkBattleUri uri, IMsgBody body, string? bid = null) => private static MsgEnvelope EnvelopeForPush(NetworkBattleUri uri, IMsgBody body, string? bid = null) =>
new(uri, new(uri,
ViewerId: FakeOpponentViewerId, ViewerId: FakeOpponentViewerId,
Uuid: "node-stub", Uuid: WireConstants.ServerUuid,
Bid: bid, Bid: bid,
Try: 0, Try: 0,
Cat: EmitCategory.Battle, Cat: EmitCategory.Battle,

View File

@@ -0,0 +1,27 @@
namespace SVSim.BattleNode.Protocol;
/// <summary>
/// String constants that show up on the wire as opaque tags. Lifting them out of
/// inline string literals gives each one a single source of truth and a name that
/// reads at the use site.
/// </summary>
internal static class WireConstants
{
/// <summary>SIO event name for ordered server-pushed frames (the lifecycle channel).</summary>
public const string SynchronizeEvent = "synchronize";
/// <summary>SIO event name for client-emitted msg frames + their ack-responses.</summary>
public const string MsgEvent = "msg";
/// <summary>SIO event name for Gungnir keepalive frames (both directions).</summary>
public const string AliveEvent = "alive";
/// <summary>
/// Placeholder UUID we stamp on every server-originated envelope. Prod servers stamp a
/// real per-request UUID; the client doesn't validate it.
/// </summary>
public const string ServerUuid = "node-stub";
/// <summary>Gungnir scs/ocs value the v1 server reports unconditionally.</summary>
public const string OnlineStatus = "ONLINE";
}

View File

@@ -112,10 +112,10 @@ public sealed class BattleSession
{ {
switch (frame.EventName) switch (frame.EventName)
{ {
case "msg" when frame.BinaryAttachments.Count == 1: case WireConstants.MsgEvent when frame.BinaryAttachments.Count == 1:
await HandleMsgEventAsync(frame); await HandleMsgEventAsync(frame);
return; return;
case "alive" when frame.BinaryAttachments.Count == 1: case WireConstants.AliveEvent when frame.BinaryAttachments.Count == 1:
await HandleAliveEventAsync(frame); await HandleAliveEventAsync(frame);
return; return;
} }
@@ -178,14 +178,14 @@ public sealed class BattleSession
var aliveEnv = new MsgEnvelope( var aliveEnv = new MsgEnvelope(
Uri: NetworkBattleUri.Gungnir, Uri: NetworkBattleUri.Gungnir,
ViewerId: ScriptedLifecycle.FakeOpponentViewerId, ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
Uuid: "node-stub", Uuid: WireConstants.ServerUuid,
Bid: null, Bid: null,
Try: 0, Try: 0,
Cat: EmitCategory.General, Cat: EmitCategory.General,
PubSeq: null, PubSeq: null,
PlaySeq: null, PlaySeq: null,
Body: new AlivePushBody(Scs: "ONLINE", Ocs: "ONLINE")); Body: new AlivePushBody(Scs: WireConstants.OnlineStatus, Ocs: WireConstants.OnlineStatus));
await PushNoStockAsync(aliveEnv, eventName: "alive"); await PushNoStockAsync(aliveEnv, eventName: WireConstants.AliveEvent);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -272,7 +272,7 @@ public sealed class BattleSession
private MsgEnvelope BuildAckedEnvelope(NetworkBattleUri uri) => new( private MsgEnvelope BuildAckedEnvelope(NetworkBattleUri uri) => new(
uri, uri,
ViewerId: ScriptedLifecycle.FakeOpponentViewerId, ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
Uuid: "node-stub", Uuid: WireConstants.ServerUuid,
Bid: null, Bid: null,
Try: 0, Try: 0,
Cat: EmitCategory.General, Cat: EmitCategory.General,
@@ -283,7 +283,7 @@ public sealed class BattleSession
private MsgEnvelope BuildBattleFinishNoContest() => new( private MsgEnvelope BuildBattleFinishNoContest() => new(
NetworkBattleUri.BattleFinish, NetworkBattleUri.BattleFinish,
ViewerId: ScriptedLifecycle.FakeOpponentViewerId, ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
Uuid: "node-stub", Uuid: WireConstants.ServerUuid,
Bid: null, Bid: null,
Try: 0, Try: 0,
Cat: EmitCategory.Battle, Cat: EmitCategory.Battle,
@@ -317,10 +317,10 @@ public sealed class BattleSession
return Array.Empty<long>(); return Array.Empty<long>();
} }
private Task PushOrderedAsync(MsgEnvelope env, string eventName = "synchronize") => private Task PushOrderedAsync(MsgEnvelope env, string eventName = WireConstants.SynchronizeEvent) =>
EncodeAndSendAsync(Outbound.AssignAndArchive(env), eventName); EncodeAndSendAsync(Outbound.AssignAndArchive(env), eventName);
private Task PushNoStockAsync(MsgEnvelope env, string eventName = "synchronize") => private Task PushNoStockAsync(MsgEnvelope env, string eventName = WireConstants.SynchronizeEvent) =>
EncodeAndSendAsync(Outbound.WrapNoStock(env), eventName); EncodeAndSendAsync(Outbound.WrapNoStock(env), eventName);
private async Task EncodeAndSendAsync(MsgEnvelope env, string eventName) private async Task EncodeAndSendAsync(MsgEnvelope env, string eventName)