#!/usr/bin/env python3 """Capture the roster oracle's responses byte-for-byte. generate_roster.py [--check] [host:port] Unlike `generate.py`, which imports the Blaze responder and calls its pure functions, this captures over the wire. The roster response is shaped as much by `http.server.BaseHTTPRequestHandler` as by the handler code -- HTTP/1.0 status line, `Server:`/`Date:` injected ahead of the handler's own headers, POST answered without a body -- and only the real socket shows all of that. Two fields are volatile and are MASKED rather than recorded: Date: changes every second Server: carries the container's Python version They are masked, not dropped, so their presence and position are still asserted. The Server string is additionally recorded verbatim under `observed_server`, so a drift between the container's Python and the adapter's `ORACLE_SERVER` constant is visible rather than silent. `--check` re-captures and compares. If the oracle is unreachable it FAILS rather than passing: a check that cannot check must not report success. """ import json import os import re import socket import ssl import sys HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(HERE, "roster.json") PATH = "/fifa17/fut/rosterupdate.xml" DATE_RE = re.compile(rb"^Date: .+?\r\n", re.M) SERVER_RE = re.compile(rb"^Server: (.+?)\r\n", re.M) def fetch(host, port, method, body=None): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE ctx.set_ciphers("ALL:@SECLEVEL=0") s = ctx.wrap_socket(socket.create_connection((host, port), timeout=8), server_hostname="fixture") req = "%s %s HTTP/1.1\r\nHost: %s:%d\r\nAccept: */*\r\n" % (method, PATH, host, port) if body is not None: req += "Content-Length: %d\r\n" % len(body) req += "\r\n" s.sendall(req.encode() + (body or b"")) out = b"" while True: chunk = s.recv(4096) if not chunk: break out += chunk s.close() return out def capture(host, port): result = {"path": PATH, "responses": {}} servers = set() for method, body in (("GET", None), ("HEAD", None), ("POST", b"probe=1")): raw = fetch(host, port, method, body) m = SERVER_RE.search(raw) if m: servers.add(m.group(1).decode()) masked = DATE_RE.sub(b"Date: \r\n", raw) masked = SERVER_RE.sub(b"Server: \r\n", masked) result["responses"][method] = masked.hex() if len(servers) != 1: raise SystemExit("oracle returned inconsistent Server headers: %r" % servers) result["observed_server"] = servers.pop() return result def main(): check = "--check" in sys.argv args = [a for a in sys.argv[1:] if not a.startswith("--")] host, port = (args[0].split(":") if args else ("127.0.0.1", "8081"))[0], \ int((args[0].split(":")[1] if args and ":" in args[0] else "8081")) try: fresh = capture(host, port) except Exception as e: # Explicitly a failure. A --check that silently passes when it could not # reach the oracle is exactly the class of self-confirming tooling this # project has been bitten by repeatedly. raise SystemExit("cannot reach the roster oracle at %s:%d (%s). " "Refusing to report success." % (host, port, e)) if check: if not os.path.exists(OUT): raise SystemExit("no fixture at %s -- run without --check first" % OUT) with open(OUT) as f: stored = json.load(f) if stored.get("responses") != fresh["responses"]: for m in sorted(set(stored.get("responses", {})) | set(fresh["responses"])): a = stored.get("responses", {}).get(m) b = fresh["responses"].get(m) if a != b: print("MISMATCH %s\n stored: %s\n live : %s" % (m, a, b)) raise SystemExit("roster fixtures differ from the live oracle") if stored.get("observed_server") != fresh["observed_server"]: raise SystemExit( "the oracle's Server header changed: %r -> %r.\n" "Update roster::ORACLE_SERVER and regenerate." % (stored.get("observed_server"), fresh["observed_server"])) print("roster fixtures match the live oracle (%d responses, server=%r)" % (len(fresh["responses"]), fresh["observed_server"])) return with open(OUT, "w") as f: json.dump(fresh, f, indent=2, sort_keys=True) f.write("\n") print("wrote %s (%d responses, server=%r)" % (OUT, len(fresh["responses"]), fresh["observed_server"])) if __name__ == "__main__": main()