"""Adapters: other inputs seen as the streams the engine understands. JSON-RPC (chain nodes, and any JSON-RPC API): one call -> one canonical request text for the HTTP tiers ('post /rpc | m: | p:') plus the flow-like event the entity stream takes (client -> node, 'port' = method hash, 'no reply' = error response or expensive method), so RPC floods, method enumeration and spam show up as behaviour per client without any chain-specific model. """ from __future__ import annotations import json import zlib from dibaguard.http import canon from dibaguard.shield.playbook import EXPENSIVE_RPC, match_rpc def rpc_text(body: str | bytes, path: str = "/rpc") -> tuple[str, int, list[str]]: """-> (canonical text, deepest decoding layer, playbook ids)""" try: obj = json.loads(body) except (ValueError, TypeError): text, deep = canon.canon(body if isinstance(body, str) else body.decode("utf-8", "replace")) return f"post {path} | b:{text[:4096]}", deep, [] calls = obj if isinstance(obj, list) else [obj] parts, ids, deepest = [], [], 0 for c in calls[:50]: if not isinstance(c, dict): continue m = str(c.get("method", "")) ids += match_rpc(m) p = c.get("params", []) ptxt, deep = canon.canon(json.dumps(p, ensure_ascii=False)[:2048]) deepest = max(deepest, deep) parts.append(f"m:{m.lower()} | p:{ptxt}") text = f"post {path} | " + " | ".join(parts) + (f" | batch:{len(calls)}" if len(calls) > 1 else "") return text[:8192], deepest, sorted(set(ids)) def rpc_event(method: str, client: str, node: str, t: float, request_bytes: int, error: bool) -> dict: """one flow-like event for Guard.flows(): the method is the 'service', an error or an expensive method is 'no reply'""" m = method.lower() expensive = any(m.startswith(e) for e in EXPENSIVE_RPC) port = 1024 + (zlib.crc32(m.encode()) % 60000) return {"t": t, "src": client, "dst": node, "dport": port, "proto": 6, "bytes_in": float(request_bytes), "pkts_in": 1.0, "bytes_out": 0.0 if (error or expensive) else 200.0, "pkts_out": 0.0 if (error or expensive) else 1.0, "client_flags": 27, "server_flags": 0 if error else 27, "flags": 27, "duration_ms": 5.0}