From cc32223d7d998b0c5ffd468e7965b8723bc321a6 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Mon, 1 Jun 2026 01:48:52 -0400 Subject: [PATCH] fix(battle-node): strip/prepend EIO3 type byte on binary WS frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Engine.IO v3 frames over WebSocket prepend the packet-type byte (0x04 for Message) to BINARY frames, the binary analog of the leading digit on text frames. The real client honors this and our session was treating the entire binary frame as the Socket.IO attachment payload — the msgpack decoder saw 0x04 as a positive fixint and failed deserialization on every inbound msg event. Symmetric fix: strip 0x04 from inbound binary frames in BattleSession.RunAsync, prepend 0x04 to outbound binary frames in EncodeAndSendAsync. RawSocketIoTestClient gets the same on both directions so the integration test still exercises the same wire shape as a real client. Caught during v1 smoke walkthrough, after the WS upgrade started succeeding (101 Switching Protocols). Co-Authored-By: Claude Opus 4.7 --- SVSim.BattleNode/Sessions/BattleSession.cs | 19 +++++++++++++++++-- .../Integration/RawSocketIoTestClient.cs | 16 ++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/SVSim.BattleNode/Sessions/BattleSession.cs b/SVSim.BattleNode/Sessions/BattleSession.cs index dad9c37..1c3d086 100644 --- a/SVSim.BattleNode/Sessions/BattleSession.cs +++ b/SVSim.BattleNode/Sessions/BattleSession.cs @@ -72,7 +72,15 @@ public sealed class BattleSession else { // Binary frame — an attachment for a pending binary event. - pendingAttachments.Add(msg.Value.Bytes); + // Engine.IO v3 prefixes binary WS frames with the packet-type byte + // (0x04 = Message), analogous to the leading digit on text frames. + // Strip it before treating the rest as the Socket.IO attachment payload. + var bin = msg.Value.Bytes; + if (bin.Length > 0 && bin[0] == (byte)EngineIoPacketType.Message) + { + bin = bin.AsSpan(1).ToArray(); + } + pendingAttachments.Add(bin); if (pendingFrame is not null && pendingAttachments.Count == pendingFrame.AttachmentCount) { var assembled = pendingFrame.WithAttachments(pendingAttachments.ToArray()); @@ -248,7 +256,14 @@ public sealed class BattleSession var eioText = $"{(int)EngineIoPacketType.Message}{text}"; await SendTextAsync(eioText, CancellationToken.None); foreach (var bin in bins) - await _ws.SendAsync(bin, WebSocketMessageType.Binary, endOfMessage: true, CancellationToken.None); + { + // Engine.IO v3 binary frames are prefixed with the packet-type byte + // (0x04 = Message), the binary analog of the leading digit on text frames. + var prefixed = new byte[bin.Length + 1]; + prefixed[0] = (byte)EngineIoPacketType.Message; + Buffer.BlockCopy(bin, 0, prefixed, 1, bin.Length); + await _ws.SendAsync(prefixed, WebSocketMessageType.Binary, endOfMessage: true, CancellationToken.None); + } } private async Task SendSioAckAsync(int ackId, long arg) diff --git a/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs b/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs index 961c554..2dd416a 100644 --- a/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs +++ b/SVSim.UnitTests/BattleNode/Integration/RawSocketIoTestClient.cs @@ -66,7 +66,13 @@ internal sealed class RawSocketIoTestClient : IAsyncDisposable } await SendTextAsync($"{(int)EngineIoPacketType.Message}{text}", ct); foreach (var b in bins) - await _ws.SendAsync(b, WebSocketMessageType.Binary, true, ct); + { + // EIO v3 binary frames are prefixed with the packet-type byte (0x04 = Message). + var prefixed = new byte[b.Length + 1]; + prefixed[0] = (byte)EngineIoPacketType.Message; + Buffer.BlockCopy(b, 0, prefixed, 1, b.Length); + await _ws.SendAsync(prefixed, WebSocketMessageType.Binary, true, ct); + } } private async Task ReceiveTextAsync(CancellationToken ct) @@ -92,7 +98,13 @@ internal sealed class RawSocketIoTestClient : IAsyncDisposable result = await _ws.ReceiveAsync(buffer, ct); ms.Write(buffer, 0, result.Count); } while (!result.EndOfMessage); - return ms.ToArray(); + var raw = ms.ToArray(); + // EIO v3 binary frames are prefixed with the packet-type byte (0x04 = Message). Strip it. + if (raw.Length > 0 && raw[0] == (byte)EngineIoPacketType.Message) + { + return raw.AsSpan(1).ToArray(); + } + return raw; } private Task SendTextAsync(string text, CancellationToken ct)