Files
OpenFUT/openfut-blaze-host/route-ab.py
T
funman300 a84a72e0c0 blaze-host: A/B the RPC routes a real FIFA session actually used
The gate 5-6 live run exercised 14 RPCs the recorded fixtures never covered --
Stats, Clubs, OSDKSettings, SponsoredEvents, Messaging::fetchMessages,
Util::getTelemetryServer, Util::userSettingsLoadAll, UserSessions cmd 0x0008,
and the transport PING -- every one taking the empty-reply fallback.

That Python does the same was an inference from reading its dispatch table.
This sends those exact routes to both backends and diffs the replies:
14/14 byte-identical.

Worth keeping: the fixtures were built from what the responder implements, so
they could never have covered what the client asks for and the responder does
not. Only a live session reveals that surface, and this makes it checkable
afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 02:05:18 +00:00

93 lines
3.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Send the routes a real FIFA session used to BOTH backends and diff the replies.
./route-ab.py [python-host:port] [rust-host:port]
WHY
The gate 5-6 live session exercised 14 RPCs the recorded fixtures never covered
-- Stats, Clubs, OSDKSettings, SponsoredEvents, Messaging::fetchMessages,
Util::getTelemetryServer, the transport PING -- and every one took the
empty-reply fallback. That Python does the same was an INFERENCE from reading
its dispatch table. This turns it into evidence, using the routes FIFA actually
sent.
None of these RPCs read their request body (they are unhandled on both sides),
so sending them with an empty payload exercises the same path the client did.
Read-only: opens a client connection to each backend and sends requests. All
routes here are unimplemented on both sides, so nothing mutates.
"""
import socket, struct, sys
# (component, command, msg_type) observed in the live FIFA session.
MESSAGE, PING = 0, 4
ROUTES = [
(0x0009, 0x0005, MESSAGE, "Util::getTelemetryServer"),
(0x0009, 0x000C, MESSAGE, "Util::userSettingsLoadAll"),
(0x0009, 0x001C, MESSAGE, "Util::setClientState"),
(0x7802, 0x0008, MESSAGE, "UserSessions::cmd:0x0008"),
(0x000F, 0x0002, MESSAGE, "Messaging::fetchMessages"),
(0x000B, 0x0640, MESSAGE, "Clubs::cmd:0x0640"),
(0x000B, 0x0A28, MESSAGE, "Clubs::cmd:0x0a28"),
(0x08C9, 0x0001, MESSAGE, "OSDKSettings::cmd:0x0001"),
(0x08C9, 0x0002, MESSAGE, "OSDKSettings::cmd:0x0002"),
(0x0007, 0x0003, MESSAGE, "Stats::cmd:0x0003"),
(0x0007, 0x000F, MESSAGE, "Stats::cmd:0x000f"),
(0x0007, 0x0014, MESSAGE, "Stats::cmd:0x0014"),
(0x081C, 0x0003, MESSAGE, "SponsoredEvents::cmd:0x0003"),
(0x0000, 0x0000, PING, "transport PING"),
]
def frame(comp, cmd, num, mtype, payload=b""):
h = bytearray(16)
struct.pack_into(">I", h, 0, len(payload))
struct.pack_into(">H", h, 4, 0)
struct.pack_into(">H", h, 6, comp)
struct.pack_into(">H", h, 8, cmd)
h[10], h[11], h[12] = (num >> 16) & 0xFF, (num >> 8) & 0xFF, num & 0xFF
h[13] = (mtype & 7) << 5
return bytes(h) + payload
def ask(host, port, routes):
s = socket.create_connection((host, port), timeout=10)
s.settimeout(10)
out, buf = [], b""
for i, (comp, cmd, mtype, name) in enumerate(routes):
s.sendall(frame(comp, cmd, i + 1, mtype))
while len(buf) < 16:
d = s.recv(65536)
if not d:
out.append((name, "NO REPLY")); s.close(); return out
buf += d
total = 16 + struct.unpack_from(">H", buf, 4)[0] + struct.unpack_from(">I", buf, 0)[0]
while len(buf) < total:
buf += s.recv(65536)
out.append((name, buf[:total].hex()))
buf = buf[total:]
s.close()
return out
def endpoint(arg, default_port):
if ":" in arg:
h, p = arg.rsplit(":", 1)
return h, int(p)
return arg, default_port
args = sys.argv[1:]
PY_HOST, PY_PORT = endpoint(args[0], 42130) if len(args) > 0 else ("127.0.0.1", 42130)
RS_HOST, RS_PORT = endpoint(args[1], 42230) if len(args) > 1 else ("127.0.0.1", 42230)
print("python %s:%d rust %s:%d" % (PY_HOST, PY_PORT, RS_HOST, RS_PORT))
py = ask(PY_HOST, PY_PORT, ROUTES)
rs = ask(RS_HOST, RS_PORT, ROUTES)
bad = 0
for (n1, a), (n2, b) in zip(py, rs):
same = (a == b)
if not same:
bad += 1
print(" %-32s %s" % (n1, "identical" if same else "DIFFERS\n py=%s\n rs=%s" % (a, b)))
print("\n%d/%d identical" % (len(ROUTES) - bad, len(ROUTES)))
sys.exit(1 if bad else 0)