diff --git a/SVSim.BattleNode/Protocol/MsgEnvelope.cs b/SVSim.BattleNode/Protocol/MsgEnvelope.cs index 7baa7a6..0d505dd 100644 --- a/SVSim.BattleNode/Protocol/MsgEnvelope.cs +++ b/SVSim.BattleNode/Protocol/MsgEnvelope.cs @@ -24,9 +24,25 @@ public sealed record MsgEnvelope( // EngineIoHandshake). Every wire key here is explicit via the manual ToJson layering below. private static readonly JsonSerializerOptions Options = WireJsonOptions.CamelCase; + /// The fixed envelope wire keys, single-sourced. , + /// the writes, and the reads all draw from here, so + /// the three encodings can't drift — adding a key in one place but not another (which would let a + /// body key silently shadow an envelope field) is no longer possible. + private static class Keys + { + public const string Uri = "uri"; + public const string ViewerId = "viewerId"; + public const string Uuid = "uuid"; + public const string Bid = "bid"; + public const string Try = "try"; + public const string Cat = "cat"; + public const string PubSeq = "pubSeq"; + public const string PlaySeq = "playSeq"; + } + private static readonly HashSet ReservedEnvelopeKeys = new() { - "uri", "viewerId", "uuid", "bid", "try", "cat", "pubSeq", "playSeq", + Keys.Uri, Keys.ViewerId, Keys.Uuid, Keys.Bid, Keys.Try, Keys.Cat, Keys.PubSeq, Keys.PlaySeq, }; public static string ToJson(MsgEnvelope env) @@ -37,14 +53,14 @@ public sealed record MsgEnvelope( // field processed before "uri" is wiped before Matching.StartBattleLoad reads // it back. The prod wire emits envelope keys first; we must too. var result = new JsonObject(); - result["uri"] = env.Uri.ToString(); - result["viewerId"] = env.ViewerId; - result["uuid"] = env.Uuid; - result["try"] = env.RetryAttempt; - result["cat"] = (int)env.Cat; - if (env.Bid is not null) result["bid"] = env.Bid; - if (env.PubSeq.HasValue) result["pubSeq"] = env.PubSeq.Value; - if (env.PlaySeq.HasValue) result["playSeq"] = env.PlaySeq.Value; + result[Keys.Uri] = env.Uri.ToString(); + result[Keys.ViewerId] = env.ViewerId; + result[Keys.Uuid] = env.Uuid; + result[Keys.Try] = env.RetryAttempt; + result[Keys.Cat] = (int)env.Cat; + if (env.Bid is not null) result[Keys.Bid] = env.Bid; + if (env.PubSeq.HasValue) result[Keys.PubSeq] = env.PubSeq.Value; + if (env.PlaySeq.HasValue) result[Keys.PlaySeq] = env.PlaySeq.Value; if (env.Body is RawBody raw) { @@ -118,14 +134,14 @@ public sealed record MsgEnvelope( using var doc = JsonDocument.Parse(json); var root = doc.RootElement; - var uri = Enum.Parse(root.GetProperty("uri").GetString()!); - var viewerId = root.GetProperty("viewerId").GetInt64(); - var uuid = root.GetProperty("uuid").GetString()!; - var bid = root.TryGetProperty("bid", out var bidEl) ? bidEl.GetString() : null; - var retryAttempt = root.TryGetProperty("try", out var tryEl) ? tryEl.GetInt32() : 0; - var cat = root.TryGetProperty("cat", out var catEl) ? (EmitCategory)catEl.GetInt32() : EmitCategory.Battle; - var pubSeq = root.TryGetProperty("pubSeq", out var psEl) ? psEl.GetInt64() : (long?)null; - var playSeq = root.TryGetProperty("playSeq", out var plsEl) ? plsEl.GetInt64() : (long?)null; + var uri = Enum.Parse(root.GetProperty(Keys.Uri).GetString()!); + var viewerId = root.GetProperty(Keys.ViewerId).GetInt64(); + var uuid = root.GetProperty(Keys.Uuid).GetString()!; + var bid = root.TryGetProperty(Keys.Bid, out var bidEl) ? bidEl.GetString() : null; + var retryAttempt = root.TryGetProperty(Keys.Try, out var tryEl) ? tryEl.GetInt32() : 0; + var cat = root.TryGetProperty(Keys.Cat, out var catEl) ? (EmitCategory)catEl.GetInt32() : EmitCategory.Battle; + var pubSeq = root.TryGetProperty(Keys.PubSeq, out var psEl) ? psEl.GetInt64() : (long?)null; + var playSeq = root.TryGetProperty(Keys.PlaySeq, out var plsEl) ? plsEl.GetInt64() : (long?)null; var bodyDict = new Dictionary(); foreach (var prop in root.EnumerateObject())