#!/usr/bin/env python3 """Passive FOCAS/2 wire capture for protocol-version scoping. Sends ONLY the initiate PDU the OtOpcUa WireFocasClient sends (a read-only handshake; NO data/write PDUs) and dumps the raw response so the real wire framing — notably the PDU version field — can be inspected. Used to diagnose the 30i-B PDU-v3 gap; see docs/plans/2026-06-25-focas-pdu-v3-30i-b-support.md. Usage: python3 scripts/focas/capture-initiate.py [port] """ import socket, sys MAGIC = bytes([0xA0, 0xA0, 0xA0, 0xA0]) def build_pdu(version, type_, direction, body): h = bytearray(MAGIC) h += version.to_bytes(2, "big") h += bytes([type_, direction]) h += len(body).to_bytes(2, "big") return bytes(h) + body def hexdump(b): return " ".join(f"{x:02x}" for x in b) def parse_header(b): if len(b) < 10: return f"(short read, {len(b)} bytes)" return (f"magic={hexdump(b[0:4])} version={int.from_bytes(b[4:6],'big')} " f"type=0x{b[6]:02x} dir=0x{b[7]:02x} bodyLen={int.from_bytes(b[8:10],'big')}") def try_initiate(host, port, version, socket_index): print(f"\n=== initiate header version={version}, socketIndex={socket_index} ===") pdu = build_pdu(version, 0x01, 0x01, socket_index.to_bytes(2, "big")) print(f" -> {len(pdu)} bytes: {hexdump(pdu)}") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(6) try: s.connect((host, port)) s.sendall(pdu) resp = b"" try: while len(resp) < 512: chunk = s.recv(512 - len(resp)) if not chunk: break resp += chunk if len(resp) >= 10 and len(resp) >= 10 + int.from_bytes(resp[8:10], "big"): break except socket.timeout: pass print(f" <- {len(resp)} bytes: {hexdump(resp)}") print(f" <- header: {parse_header(resp)}") if len(resp) > 10: print(f" <- body: {hexdump(resp[10:])}") except Exception as e: print(f" !! {type(e).__name__}: {e}") finally: s.close() if __name__ == "__main__": if len(sys.argv) < 2: print(__doc__) sys.exit(2) host = sys.argv[1] port = int(sys.argv[2]) if len(sys.argv) > 2 else 8193 try_initiate(host, port, 1, 1) # what OtOpcUa sends today (header version=1) try_initiate(host, port, 3, 1) # probe whether the CNC negotiates on our advertised version