refactor(battle-node): WireConstants for SIO event names + crypto RNG battle id
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Security.Cryptography;
|
||||
using SVSim.BattleNode.Sessions;
|
||||
|
||||
namespace SVSim.BattleNode.Bridge;
|
||||
@@ -23,8 +24,13 @@ public sealed class MatchingBridge : IMatchingBridge
|
||||
public PendingMatch RegisterPendingBattle(long viewerId)
|
||||
{
|
||||
// 12-digit decimal battle id mirrors the captures (e.g. "975695075012").
|
||||
// Cast to long before Math.Abs to avoid OverflowException on int.MinValue.
|
||||
var battleId = (Math.Abs((long)Guid.NewGuid().GetHashCode()) % 1_000_000_000_000L).ToString("D12");
|
||||
// Two unbiased 6-digit draws concatenated — RandomNumberGenerator.GetInt32 uses
|
||||
// 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));
|
||||
return new PendingMatch(battleId, _options.NodeServerUrl);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public static class ScriptedLifecycle
|
||||
private static MsgEnvelope EnvelopeForPush(NetworkBattleUri uri, IMsgBody body, string? bid = null) =>
|
||||
new(uri,
|
||||
ViewerId: FakeOpponentViewerId,
|
||||
Uuid: "node-stub",
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: bid,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
|
||||
27
SVSim.BattleNode/Protocol/WireConstants.cs
Normal file
27
SVSim.BattleNode/Protocol/WireConstants.cs
Normal 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";
|
||||
}
|
||||
@@ -112,10 +112,10 @@ public sealed class BattleSession
|
||||
{
|
||||
switch (frame.EventName)
|
||||
{
|
||||
case "msg" when frame.BinaryAttachments.Count == 1:
|
||||
case WireConstants.MsgEvent when frame.BinaryAttachments.Count == 1:
|
||||
await HandleMsgEventAsync(frame);
|
||||
return;
|
||||
case "alive" when frame.BinaryAttachments.Count == 1:
|
||||
case WireConstants.AliveEvent when frame.BinaryAttachments.Count == 1:
|
||||
await HandleAliveEventAsync(frame);
|
||||
return;
|
||||
}
|
||||
@@ -178,14 +178,14 @@ public sealed class BattleSession
|
||||
var aliveEnv = new MsgEnvelope(
|
||||
Uri: NetworkBattleUri.Gungnir,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: "node-stub",
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.General,
|
||||
PubSeq: null,
|
||||
PlaySeq: null,
|
||||
Body: new AlivePushBody(Scs: "ONLINE", Ocs: "ONLINE"));
|
||||
await PushNoStockAsync(aliveEnv, eventName: "alive");
|
||||
Body: new AlivePushBody(Scs: WireConstants.OnlineStatus, Ocs: WireConstants.OnlineStatus));
|
||||
await PushNoStockAsync(aliveEnv, eventName: WireConstants.AliveEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -272,7 +272,7 @@ public sealed class BattleSession
|
||||
private MsgEnvelope BuildAckedEnvelope(NetworkBattleUri uri) => new(
|
||||
uri,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: "node-stub",
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.General,
|
||||
@@ -283,7 +283,7 @@ public sealed class BattleSession
|
||||
private MsgEnvelope BuildBattleFinishNoContest() => new(
|
||||
NetworkBattleUri.BattleFinish,
|
||||
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
|
||||
Uuid: "node-stub",
|
||||
Uuid: WireConstants.ServerUuid,
|
||||
Bid: null,
|
||||
Try: 0,
|
||||
Cat: EmitCategory.Battle,
|
||||
@@ -317,10 +317,10 @@ public sealed class BattleSession
|
||||
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);
|
||||
|
||||
private Task PushNoStockAsync(MsgEnvelope env, string eventName = "synchronize") =>
|
||||
private Task PushNoStockAsync(MsgEnvelope env, string eventName = WireConstants.SynchronizeEvent) =>
|
||||
EncodeAndSendAsync(Outbound.WrapNoStock(env), eventName);
|
||||
|
||||
private async Task EncodeAndSendAsync(MsgEnvelope env, string eventName)
|
||||
|
||||
Reference in New Issue
Block a user