From e966f4d2a3fef8d040b527839b43cfe9f82a9a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20P=C3=A9rez?= Date: Mon, 4 May 2026 04:39:23 +0000 Subject: [PATCH] feat(report): send HELLO (0xFF) JSON to monitors on connect Emit opcode 0xFF with server name, version, protocol and feature flags before CONFIG_SND/BRIDGE_SND so adn-monitor can detect v2 vs legacy. --- .../twisted_adapters/report_server.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/adn_server/infrastructure/twisted_adapters/report_server.py b/src/adn_server/infrastructure/twisted_adapters/report_server.py index 89a8933..2b62ee0 100644 --- a/src/adn_server/infrastructure/twisted_adapters/report_server.py +++ b/src/adn_server/infrastructure/twisted_adapters/report_server.py @@ -25,6 +25,7 @@ from __future__ import annotations +import json import logging import pickle from typing import Any @@ -34,7 +35,6 @@ from twisted.protocols.basic import NetstringReceiver logger = logging.getLogger(__name__) -# Same opcodes as legacy reporting_const REPORT_OPCODES = { "CONFIG_REQ": b"\x00", "CONFIG_SND": b"\x01", @@ -44,8 +44,25 @@ REPORT_OPCODES = { "BRIDGE_UPD": b"\x05", "LINK_EVENT": b"\x06", "BRDG_EVENT": b"\x07", + "HELLO": b"\xff", } +SERVER_NAME = "adn-server" +PROTOCOL_VERSION = 1 +SERVER_FEATURES = ("INGRESS", "END_TX_FORWARD", "PUSH_ON_CONNECT") + + +def _server_version() -> str: + try: + from importlib.metadata import PackageNotFoundError, version + + try: + return version("adn-server") + except PackageNotFoundError: + return "0.0.0" + except Exception: + return "0.0.0" + class ReportProtocol(NetstringReceiver): """Single report client connection.""" @@ -58,7 +75,7 @@ class ReportProtocol(NetstringReceiver): peer = self.transport.getPeer() if self.transport else None addr = f"{peer.host}:{peer.port}" if peer else "?" logger.info("(REPORT) Client connected from %s (%s client(s))", addr, len(self._factory.clients)) - # Send CONFIG_SND and BRIDGE_SND immediately so monitor gets systems/bridges without waiting for loop + self._factory._send_hello_to(self) self._factory._send_config_to(self) self._factory._send_bridge_to(self) @@ -99,6 +116,20 @@ class ReportServerFactory(Factory): def set_bridges(self, bridges: dict[str, Any]) -> None: self._bridges = bridges + def _send_hello_to(self, client: ReportProtocol) -> None: + info = { + "server": SERVER_NAME, + "version": _server_version(), + "protocol": PROTOCOL_VERSION, + "features": list(SERVER_FEATURES), + } + payload = json.dumps(info, separators=(",", ":")).encode("utf-8") + try: + client.sendString(REPORT_OPCODES["HELLO"] + payload) + logger.debug("(REPORT) Sent HELLO to client: %s", info) + except Exception as e: + logger.warning("(REPORT) Failed to send HELLO: %s", e) + def _send_config_to(self, client: ReportProtocol) -> None: """Send CONFIG_SND to a single client (e.g. on connect or CONFIG_REQ).""" payload = pickle.dumps(self._systems, protocol=2)