#!/usr/bin/env python3
import argparse
import asyncio
import base64
import json
import os
import sqlite3
import time
import ssl
import re
import hashlib
from urllib.parse import parse_qs
from dataclasses import dataclass
from typing import Optional
import websockets
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError

try:
    from websockets.http11 import Response as _WSResponse
    from websockets.datastructures import Headers as _WSHeaders
except Exception:
    _WSResponse = None
    _WSHeaders = None

def _http_response(status: int, body: bytes, content_type: str = "text/plain; charset=utf-8"):
    if _WSResponse is not None:
        hdrs = _WSHeaders(
            {
                "Content-Type": str(content_type),
                "Content-Length": str(len(body)),
            }
        )
        return _WSResponse(int(status), "OK" if int(status) == 200 else "", hdrs, body)
    return (
        int(status),
        [("Content-Type", str(content_type)), ("Content-Length", str(len(body)))],
        body,
    )

def _is_websocket_upgrade(path: str, headers) -> bool:
    try:
        upgrade = str(headers.get("Upgrade", "") if headers else "").lower()
        connection = str(headers.get("Connection", "") if headers else "").lower()
    except Exception:
        upgrade = ""
        connection = ""
    return upgrade == "websocket" and "upgrade" in connection

def make_process_request(srv: "RelayServer"):
    async def _process_request(*args, **kwargs):

        if _WSResponse is not None and len(args) >= 2:
            request = args[1]
            try:
                path = str(getattr(request, "path", "/") or "/")
            except Exception:
                path = "/"
            try:
                headers = getattr(request, "headers", None)
            except Exception:
                headers = None

            if _is_websocket_upgrade(path, headers):
                return None

            if path.startswith("/api/nodes/top") or path.startswith("/api/top-nodes"):
                try:
                    query = ""
                    if "?" in path:
                        _, query = path.split("?", 1)
                    qs = parse_qs(query)
                    limit = int((qs.get("limit") or ["10"])[0])
                except Exception:
                    limit = 10
                limit = max(1, min(limit, 50))
                payload = {
                    "ts": now_ts(),
                    "nodes": srv.get_top_nodes(limit=limit),
                }
                body = canonical_json(payload).encode("utf-8")
                return _http_response(200, body, content_type="application/json; charset=utf-8")

            body = (
                "Antch WebSocket server\n"
                f"Path: {path}\n"
                "\n"
                "This port expects a WebSocket Upgrade request.\n"
            ).encode("utf-8")
            return _http_response(200, body, content_type="text/plain; charset=utf-8")

        path = args[0] if args and isinstance(args[0], str) else "/"
        request_headers = args[1] if len(args) > 1 else {}

        if _is_websocket_upgrade(path, request_headers):
            return None

        if str(path).startswith("/api/nodes/top") or str(path).startswith("/api/top-nodes"):
            try:
                query = ""
                if "?" in str(path):
                    _, query = str(path).split("?", 1)
                qs = parse_qs(query)
                limit = int((qs.get("limit") or ["10"])[0])
            except Exception:
                limit = 10
            limit = max(1, min(limit, 50))
            payload = {"ts": now_ts(), "nodes": srv.get_top_nodes(limit=limit)}
            body = canonical_json(payload).encode("utf-8")
            return _http_response(200, body, content_type="application/json; charset=utf-8")

        body = (
            "Antch WebSocket server\n"
            f"Path: {path}\n"
            "\n"
            "This port expects a WebSocket Upgrade request.\n"
        ).encode("utf-8")
        return _http_response(200, body, content_type="text/plain; charset=utf-8")

    return _process_request

async def _process_request_friendly(*args, **kwargs):

    if _WSResponse is not None and len(args) >= 2:
        request = args[1]
        try:
            path = str(getattr(request, "path", "/") or "/")
        except Exception:
            path = "/"
        try:
            headers = getattr(request, "headers", None)
        except Exception:
            headers = None

        try:
            upgrade = str(headers.get("Upgrade", "") if headers else "").lower()
            connection = str(headers.get("Connection", "") if headers else "").lower()
        except Exception:
            upgrade = ""
            connection = ""

        if upgrade != "websocket" or "upgrade" not in connection:
            body = (
                "Antch WebSocket server\n"
                f"Path: {path}\n"
                "\n"
                "This port expects a WebSocket Upgrade request.\n"
            ).encode("utf-8")
            return _http_response(200, body)

        return None

    path = args[0] if args and isinstance(args[0], str) else "/"
    request_headers = args[1] if len(args) > 1 else {}
    try:
        upgrade = str(getattr(request_headers, "get", lambda k, d=None: d)("Upgrade", "") or "").lower()
        connection = str(getattr(request_headers, "get", lambda k, d=None: d)("Connection", "") or "").lower()
    except Exception:
        upgrade = ""
        connection = ""

    if upgrade != "websocket" or "upgrade" not in connection:
        body = (
            "Antch WebSocket server\n"
            f"Path: {path}\n"
            "\n"
            "This port expects a WebSocket Upgrade request.\n"
        ).encode("utf-8")
        return _http_response(200, body)

    return None

MAX_CLOUD_DB_BYTES = 1024 * 1024 * 1024
MAX_CLOUD_FILE_BYTES = 1024 * 1024 * 1024
MAX_NONCE_LEN = 256
MAX_CLOUD_WAITER_AGE_SEC = 600

MAX_META_WAITER_AGE_SEC = 120

MAX_CLOUD_CHUNK_B64 = 30 * 1024 * 1024

MAX_NODE_ID_LEN = 80
MAX_NODE_DESC_LEN = 200

def b64e(b: bytes) -> str:
    return base64.b64encode(b).decode("ascii")

def b64d(s: str) -> bytes:
    return base64.b64decode(s.encode("ascii"))

def now_ts() -> int:
    return int(time.time())

def canonical_json(obj) -> str:
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)

def _decode_b64_sig_ed25519(sig_b64: str) -> Optional[bytes]:
    if not isinstance(sig_b64, str) or not sig_b64:
        return None

    if len(sig_b64) > 256:
        return None
    try:
        raw = b64d(sig_b64)
    except Exception:
        return None
    if len(raw) != 64:
        return None
    return raw

def compute_file_id_from_container(blob: bytes) -> Optional[str]:
    try:
        h = hashlib.sha256()
        off = 0
        n = len(blob)
        while off + 4 <= n:
            ln = int.from_bytes(blob[off : off + 4], "big")
            off += 4
            if ln <= 0 or ln > 64 * 1024 * 1024:
                return None
            if off + ln > n:
                return None
            rec = blob[off : off + ln]
            off += ln
            h.update(rec)
        if off != n:
            return None
        return h.hexdigest()
    except Exception:
        return None

@dataclass
class Account:
    alias: str
    fp: str
    sign_pub_b64: str
    dh_pub_b64: str
    created_at: int
    updated_at: int

class DB:
    def __init__(self, path: str):
        self.path = path
        self.conn = sqlite3.connect(self.path, check_same_thread=False)
        self.conn.execute("PRAGMA journal_mode=WAL;")
        self.conn.execute("PRAGMA synchronous=NORMAL;")
        self._init_schema()

    def _init_schema(self):
        cur = self.conn.cursor()
        cur.executescript("""
            CREATE TABLE IF NOT EXISTS accounts (
              alias TEXT PRIMARY KEY, fp TEXT NOT NULL, sign_pub_b64 TEXT NOT NULL,
              dh_pub_b64 TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
            );
            CREATE TABLE IF NOT EXISTS offline_queue (
              id INTEGER PRIMARY KEY AUTOINCREMENT, to_alias TEXT NOT NULL,
              payload_json TEXT NOT NULL, created_at INTEGER NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_offline_to ON offline_queue(to_alias, id);
            CREATE TABLE IF NOT EXISTS profiles (
                alias TEXT PRIMARY KEY, status TEXT DEFAULT 'online', status_msg TEXT DEFAULT '',
                bio TEXT DEFAULT '', visible INTEGER DEFAULT 1,
                FOREIGN KEY(alias) REFERENCES accounts(alias)
            );
            CREATE TABLE IF NOT EXISTS blocks (
                blocker TEXT NOT NULL, blocked TEXT NOT NULL,
                PRIMARY KEY(blocker, blocked)
            );

            CREATE TABLE IF NOT EXISTS cloud_index (
                owner_alias TEXT NOT NULL,
                kind TEXT NOT NULL,
                blob_sha256 TEXT NOT NULL,
                file_id TEXT DEFAULT '',
                bytes_total INTEGER NOT NULL,
                created_at INTEGER NOT NULL,
                sig_b64 TEXT DEFAULT '',
                PRIMARY KEY(owner_alias, kind, blob_sha256)
            );
            CREATE INDEX IF NOT EXISTS idx_cloud_latest ON cloud_index(owner_alias, kind, created_at DESC);
            CREATE INDEX IF NOT EXISTS idx_cloud_file_id ON cloud_index(kind, file_id, created_at DESC);

            CREATE TABLE IF NOT EXISTS node_bans (
                node_id TEXT PRIMARY KEY,
                reason TEXT NOT NULL,
                banned_at INTEGER NOT NULL
            );

            CREATE TABLE IF NOT EXISTS node_keys (
                node_id TEXT PRIMARY KEY,
                sign_pub_b64 TEXT NOT NULL,
                created_at INTEGER NOT NULL,
                updated_at INTEGER NOT NULL
            );

            /* Minimal group metadata for server-side authorization of group call actions. */
            CREATE TABLE IF NOT EXISTS groups (
                group_id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                creator TEXT NOT NULL,
                members_json TEXT NOT NULL,
                created_at INTEGER NOT NULL,
                updated_at INTEGER NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_groups_updated ON groups(updated_at DESC);
        """)

        try:
            cur.execute("PRAGMA table_info(cloud_index)")
            cols = [r[1] for r in cur.fetchall() if r and len(r) > 1]
            if "sig_b64" not in cols:
                cur.execute("ALTER TABLE cloud_index ADD COLUMN sig_b64 TEXT DEFAULT ''")
            if "file_id" not in cols:
                cur.execute("ALTER TABLE cloud_index ADD COLUMN file_id TEXT DEFAULT ''")
        except Exception:
            pass

        self.conn.commit()

    def get_group(self, group_id: str) -> Optional[dict]:
        try:
            gid = str(group_id)
            cur = self.conn.cursor()
            cur.execute(
                "SELECT group_id, name, creator, members_json, created_at, updated_at FROM groups WHERE group_id=?",
                (gid,),
            )
            row = cur.fetchone()
            if not row:
                return None
            try:
                members = json.loads(row[3] or "[]")
                if not isinstance(members, list):
                    members = []
            except Exception:
                members = []
            members = [str(a) for a in members if isinstance(a, str) and a]
            return {
                "group_id": str(row[0]),
                "name": str(row[1]),
                "creator": str(row[2]),
                "members": members,
                "created_at": int(row[4]),
                "updated_at": int(row[5]),
            }
        except Exception:
            return None

    def save_group(self, g: dict) -> None:
        try:
            if not isinstance(g, dict):
                return
            gid = str(g.get("group_id") or "")
            if not gid:
                return
            name = str(g.get("name") or "Group")
            creator = str(g.get("creator") or "")
            members = g.get("members")
            if not isinstance(members, list):
                members = []
            members = [str(a) for a in members if isinstance(a, str) and a]
            members_json = json.dumps(members, separators=(",", ":"))

            ts = now_ts()
            cur = self.conn.cursor()
            cur.execute(
                "SELECT created_at FROM groups WHERE group_id=?",
                (gid,),
            )
            row = cur.fetchone()
            created_at = int(row[0]) if row and row[0] else ts
            cur.execute(
                "INSERT OR REPLACE INTO groups(group_id, name, creator, members_json, created_at, updated_at) VALUES(?,?,?,?,?,?)",
                (gid, name, creator, members_json, created_at, ts),
            )
            self.conn.commit()
        except Exception:
            try:
                self.conn.rollback()
            except Exception:
                pass

    def get_node_sign_pub_b64(self, node_id: str) -> Optional[str]:
        try:
            cur = self.conn.cursor()
            cur.execute("SELECT sign_pub_b64 FROM node_keys WHERE node_id=?", (str(node_id),))
            row = cur.fetchone()
            if not row or not row[0]:
                return None
            return str(row[0])
        except Exception:
            return None

    def upsert_node_sign_pub_b64(self, node_id: str, sign_pub_b64: str) -> None:
        ts = now_ts()
        try:
            cur = self.conn.cursor()
            cur.execute("SELECT 1 FROM node_keys WHERE node_id=?", (str(node_id),))
            exists = cur.fetchone() is not None
            if exists:
                cur.execute(
                    "UPDATE node_keys SET sign_pub_b64=?, updated_at=? WHERE node_id=?",
                    (str(sign_pub_b64), ts, str(node_id)),
                )
            else:
                cur.execute(
                    "INSERT INTO node_keys(node_id, sign_pub_b64, created_at, updated_at) VALUES(?,?,?,?)",
                    (str(node_id), str(sign_pub_b64), ts, ts),
                )
            self.conn.commit()
        except Exception:
            pass

    def upsert_account(self, alias: str, fp: str, sign_pub_b64: str, dh_pub_b64: str):
        ts = now_ts()
        cur = self.conn.cursor()
        cur.execute("SELECT sign_pub_b64 FROM accounts WHERE alias=?", (alias,))
        row = cur.fetchone()
        if row is None:
            cur.execute("INSERT INTO accounts VALUES(?,?,?,?,?,?)", (alias, fp, sign_pub_b64, dh_pub_b64, ts, ts))
            cur.execute("INSERT OR IGNORE INTO profiles(alias) VALUES(?)", (alias,))
        else:
            cur.execute("UPDATE accounts SET fp=?, sign_pub_b64=?, dh_pub_b64=?, updated_at=? WHERE alias=?", (fp, sign_pub_b64, dh_pub_b64, ts, alias))
        self.conn.commit()

    def update_profile(self, alias: str, status: str, status_msg: str, bio: str, visible: int):
        self.conn.execute("INSERT OR IGNORE INTO profiles(alias) VALUES(?)", (alias,))
        self.conn.execute("UPDATE profiles SET status=?, status_msg=?, bio=?, visible=? WHERE alias=?", (status, status_msg, bio, visible, alias))
        self.conn.commit()

    def get_profile(self, alias: str):
        cur = self.conn.cursor()
        cur.execute("SELECT status, status_msg, bio, visible FROM profiles WHERE alias=?", (alias,))
        return cur.fetchone()

    def block_user(self, blocker: str, blocked: str):
        self.conn.execute("INSERT OR IGNORE INTO blocks VALUES(?,?)", (blocker, blocked))
        self.conn.commit()

    def unblock_user(self, blocker: str, blocked: str):
        self.conn.execute("DELETE FROM blocks WHERE blocker=? AND blocked=?", (blocker, blocked))
        self.conn.commit()

    def is_blocked(self, blocker: str, blocked: str) -> bool:
        cur = self.conn.cursor()
        cur.execute("SELECT 1 FROM blocks WHERE blocker=? AND blocked=?", (blocker, blocked))
        return cur.fetchone() is not None

    def get_all_users(self):
        cur = self.conn.cursor()

        cur.execute("""
            SELECT a.alias, a.fp, p.status, p.status_msg, p.bio, p.visible
            FROM accounts a
            LEFT JOIN profiles p ON a.alias = p.alias
        """)
        return cur.fetchall()

    def get_account(self, alias: str) -> Optional[Account]:
        cur = self.conn.cursor()
        cur.execute("SELECT * FROM accounts WHERE alias=?", (alias,))
        row = cur.fetchone()
        return Account(*row) if row else None

    def get_alias_by_fp(self, fp: str) -> Optional[str]:
        cur = self.conn.cursor()
        cur.execute("SELECT alias FROM accounts WHERE fp=?", (fp,))
        row = cur.fetchone()
        return str(row[0]) if row and row[0] else None

    def list_blocked(self, blocker: str) -> list[str]:
        cur = self.conn.cursor()
        cur.execute("SELECT blocked FROM blocks WHERE blocker=? ORDER BY blocked ASC", (blocker,))
        return [str(r[0]) for r in cur.fetchall() if r and r[0]]

    def is_node_banned(self, node_id: str) -> bool:
        cur = self.conn.cursor()
        cur.execute("SELECT 1 FROM node_bans WHERE node_id=?", (node_id,))
        return cur.fetchone() is not None

    def ban_node(self, node_id: str, reason: str):
        self.conn.execute(
            "INSERT OR REPLACE INTO node_bans(node_id, reason, banned_at) VALUES(?,?,?)",
            (node_id, str(reason or "banned"), now_ts()),
        )
        self.conn.commit()

    def add_cloud_index(
        self,
        owner_alias: str,
        kind: str,
        blob_sha256: str,
        bytes_total: int,
        created_at: int,
        sig_b64: str = "",
        file_id: str = "",
    ):
        self.conn.execute(
            "INSERT OR REPLACE INTO cloud_index(owner_alias, kind, blob_sha256, file_id, bytes_total, created_at, sig_b64) VALUES(?,?,?,?,?,?,?)",
            (owner_alias, kind, blob_sha256, str(file_id or ""), int(bytes_total), int(created_at), str(sig_b64 or "")),
        )
        self.conn.commit()

    def delete_cloud_index(self, owner_alias: str, kind: str, blob_sha256: str) -> None:
        try:
            self.conn.execute(
                "DELETE FROM cloud_index WHERE owner_alias=? AND kind=? AND blob_sha256=?",
                (str(owner_alias), str(kind), str(blob_sha256)),
            )
            self.conn.commit()
        except Exception:
            pass

    def latest_cloud_blob(self, owner_alias: str, kind: str) -> Optional[dict]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT owner_alias, kind, blob_sha256, file_id, bytes_total, created_at, sig_b64 FROM cloud_index WHERE owner_alias=? AND kind=? ORDER BY created_at DESC LIMIT 1",
            (owner_alias, kind),
        )
        r = cur.fetchone()
        if not r:
            return None
        return {
            "owner_alias": r[0],
            "kind": r[1],
            "blob_sha256": r[2],
            "file_id": r[3] if len(r) > 3 else "",
            "bytes_total": r[4] if len(r) > 4 else 0,
            "created_at": r[5] if len(r) > 5 else 0,
            "sig_b64": r[6] if len(r) > 6 else "",
        }

    def list_cloud_blobs_since(self, since_ts: int, limit: int = 500) -> list[dict]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT owner_alias, kind, blob_sha256, file_id, bytes_total, created_at, sig_b64 FROM cloud_index WHERE created_at>=? ORDER BY created_at DESC LIMIT ?",
            (int(since_ts), int(limit)),
        )
        rows = cur.fetchall()
        return [
            {
                "owner_alias": r[0],
                "kind": r[1],
                "blob_sha256": r[2],
                "file_id": r[3] if len(r) > 3 else "",
                "bytes_total": r[4] if len(r) > 4 else 0,
                "created_at": r[5] if len(r) > 5 else 0,
                "sig_b64": r[6] if len(r) > 6 else "",
            }
            for r in rows
        ]

    def list_cloud_blobs(self, owner_alias: str, kind: str, limit: int = 5000) -> list[dict]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT owner_alias, kind, blob_sha256, file_id, bytes_total, created_at, sig_b64 FROM cloud_index WHERE owner_alias=? AND kind=? ORDER BY created_at DESC LIMIT ?",
            (str(owner_alias), str(kind), int(limit)),
        )
        rows = cur.fetchall()
        return [
            {
                "owner_alias": r[0],
                "kind": r[1],
                "blob_sha256": r[2],
                "file_id": r[3] if len(r) > 3 else "",
                "bytes_total": r[4] if len(r) > 4 else 0,
                "created_at": r[5] if len(r) > 5 else 0,
                "sig_b64": r[6] if len(r) > 6 else "",
            }
            for r in rows
        ]

    def get_cloud_blob_by_sha(self, kind: str, blob_sha256: str) -> Optional[dict]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT owner_alias, kind, blob_sha256, file_id, bytes_total, created_at, sig_b64 FROM cloud_index WHERE kind=? AND blob_sha256=? LIMIT 1",
            (str(kind), str(blob_sha256)),
        )
        r = cur.fetchone()
        if not r:
            return None
        return {
            "owner_alias": r[0],
            "kind": r[1],
            "blob_sha256": r[2],
            "file_id": r[3] if len(r) > 3 else "",
            "bytes_total": r[4] if len(r) > 4 else 0,
            "created_at": r[5] if len(r) > 5 else 0,
            "sig_b64": r[6] if len(r) > 6 else "",
        }

    def get_cloud_file_by_file_id(self, file_id: str) -> Optional[dict]:
        cur = self.conn.cursor()
        cur.execute(
            "SELECT owner_alias, kind, blob_sha256, file_id, bytes_total, created_at, sig_b64 FROM cloud_index WHERE kind='file' AND file_id=? ORDER BY created_at DESC LIMIT 1",
            (str(file_id),),
        )
        r = cur.fetchone()
        if not r:
            return None
        return {
            "owner_alias": r[0],
            "kind": r[1],
            "blob_sha256": r[2],
            "file_id": r[3] if len(r) > 3 else "",
            "bytes_total": r[4] if len(r) > 4 else 0,
            "created_at": r[5] if len(r) > 5 else 0,
            "sig_b64": r[6] if len(r) > 6 else "",
        }

    def delete_account(self, alias: str):
        self.conn.execute("DELETE FROM accounts WHERE alias=?", (alias,))
        self.conn.execute("DELETE FROM profiles WHERE alias=?", (alias,))
        self.conn.execute("DELETE FROM offline_queue WHERE to_alias=?", (alias,))
        self.conn.execute("DELETE FROM blocks WHERE blocker=? OR blocked=?", (alias, alias))
        self.conn.commit()

    def queue_payload(self, to_alias: str, payload: dict):
        self.conn.execute("INSERT INTO offline_queue(to_alias, payload_json, created_at) VALUES(?,?,?)", (to_alias, canonical_json(payload), now_ts()))
        self.conn.commit()

    def pop_offline(self, to_alias: str, limit: int = 500):
        cur = self.conn.cursor()
        cur.execute("SELECT id, payload_json FROM offline_queue WHERE to_alias=? ORDER BY id ASC LIMIT ?", (to_alias, limit))
        rows = cur.fetchall()
        if not rows: return []
        ids = [r[0] for r in rows]
        payloads = [json.loads(r[1]) for r in rows]
        cur.execute(f"DELETE FROM offline_queue WHERE id IN ({','.join(['?']*len(ids))})", ids)
        self.conn.commit()
        return payloads

class RelayServer:
    def __init__(self, db: DB):
        self.db = db
        self.online: dict[str, any] = {}
        self.last_seen: dict[str, float] = {}
        self.challenges: dict[any, dict] = {}

        self.file_seeds: dict[str, set[str]] = {}

        self.seed_files: dict[str, set[str]] = {}

        self.ip_counts: dict[str, int] = {}
        self.MAX_CONNS_PER_IP = 10
        self.MAX_MSGS_PER_SEC = 100
        self.MAX_FILE_RECORD_B64 = 256 * 1024
        self._HEX64 = re.compile(r"^[0-9a-fA-F]{64}$")

        self.nodes: dict[str, any] = {}
        self.node_caps: dict[str, dict] = {}
        self._ws_node_id: dict[any, str] = {}
        self._node_stats: dict[str, dict] = {}
        self._cloud_waiters: dict[str, dict] = {}

        self._meta_waiters: dict[str, dict] = {}

        self._cloud_put_inflight: dict[tuple[str, str], dict] = {}

        self.group_calls: dict[str, dict] = {}

        self._alias_in_group_call: dict[str, str] = {}

        self._debug = str(os.getenv("ANTCH_DEBUG") or "").strip().lower() in ("1", "true", "yes", "y", "on")

    def _node_ensure(self, node_id: str) -> dict:
        st = self._node_stats.get(node_id)
        if isinstance(st, dict):
            return st
        st = {
            "description": "",
            "connected": False,
            "connected_at": 0.0,
            "last_seen": 0.0,
            "sign_pub_b64": "",
            "rtt_ema_ms": None,
            "fetch_ok": 0,
            "fetch_err": 0,
        }
        self._node_stats[node_id] = st
        return st

    def _node_touch(self, node_id: str) -> None:
        try:
            st = self._node_ensure(node_id)
            st["last_seen"] = float(time.time())
        except Exception:
            pass

    def _node_record_fetch_rtt(self, node_id: str, rtt_ms: float, ok: bool) -> None:
        st = self._node_ensure(node_id)
        try:
            rtt = float(rtt_ms)
        except Exception:
            rtt = None
        if isinstance(rtt, float) and rtt >= 0:
            prev = st.get("rtt_ema_ms")
            if isinstance(prev, (int, float)):
                st["rtt_ema_ms"] = (0.8 * float(prev)) + (0.2 * float(rtt))
            else:
                st["rtt_ema_ms"] = float(rtt)
        try:
            if ok:
                st["fetch_ok"] = int(st.get("fetch_ok") or 0) + 1
            else:
                st["fetch_err"] = int(st.get("fetch_err") or 0) + 1
        except Exception:
            pass
        self._node_touch(node_id)

    def _node_score(self, node_id: str, now: float | None = None) -> float:
        st = self._node_stats.get(node_id) or {}
        try:
            now_t = float(now if now is not None else time.time())
        except Exception:
            now_t = time.time()

        try:
            connected_at = float(st.get("connected_at") or 0.0)
        except Exception:
            connected_at = 0.0
        uptime_sec = max(0.0, now_t - connected_at) if connected_at else 0.0

        try:
            last_seen = float(st.get("last_seen") or 0.0)
        except Exception:
            last_seen = 0.0
        age_sec = max(0.0, now_t - last_seen) if last_seen else 1e9

        rtt = st.get("rtt_ema_ms")
        if not isinstance(rtt, (int, float)):
            rtt = 800.0

        speed = 1.0 / (1.0 + (float(rtt) / 200.0))
        uptime = min(1.0, uptime_sec / 3600.0)
        freshness = 2.718281828 ** (-(age_sec / 30.0))

        okc = int(st.get("fetch_ok") or 0)
        errc = int(st.get("fetch_err") or 0)
        fail_rate = float(errc) / float(okc + errc + 1)

        score = (50.0 * speed) + (30.0 * uptime) + (20.0 * freshness) - (20.0 * fail_rate)
        if score < 0.0:
            score = 0.0
        if score > 100.0:
            score = 100.0
        return float(score)

    def get_top_nodes(self, limit: int = 10) -> list[dict]:
        try:
            lim = int(limit)
        except Exception:
            lim = 10
        lim = max(1, min(lim, 50))

        now_t = time.time()
        rows: list[dict] = []
        for nid, ws in list(self.nodes.items()):
            if self.db.is_node_banned(nid):
                continue
            st = self._node_ensure(nid)
            caps = self.node_caps.get(nid) if isinstance(self.node_caps.get(nid), dict) else {}
            try:
                connected_at = float(st.get("connected_at") or 0.0)
            except Exception:
                connected_at = 0.0
            uptime_sec = max(0.0, now_t - connected_at) if connected_at else 0.0
            try:
                last_seen = float(st.get("last_seen") or 0.0)
            except Exception:
                last_seen = 0.0
            age_sec = max(0.0, now_t - last_seen) if last_seen else None

            rows.append(
                {
                    "node_id": str(nid),
                    "description": str(st.get("description") or ""),
                    "score": round(self._node_score(nid, now=now_t), 3),
                    "uptime_sec": int(uptime_sec),
                    "last_seen_age_sec": (None if age_sec is None else int(age_sec)),
                    "rtt_ema_ms": (None if st.get("rtt_ema_ms") is None else round(float(st.get("rtt_ema_ms")), 2)),
                    "fetch_ok": int(st.get("fetch_ok") or 0),
                    "fetch_err": int(st.get("fetch_err") or 0),
                    "caps": caps,
                }
            )

        rows.sort(key=lambda r: float(r.get("score") or 0.0), reverse=True)
        return rows[:lim]

    def _log(self, level: str, event: str, **fields) -> None:
        try:
            lvl = str(level or "INFO").upper()
        except Exception:
            lvl = "INFO"
        if lvl == "DEBUG" and not self._debug:
            return
        try:
            payload = {"ts": now_ts(), "lvl": lvl, "event": str(event)}
            for k, v in (fields or {}).items():

                if k in ("ct", "data"):
                    continue
                payload[str(k)] = v
            print(canonical_json(payload), flush=True)
        except Exception:
            try:
                print(f"[{lvl}] {event} {fields}", flush=True)
            except Exception:
                pass

    def _cleanup_cloud_put_inflight(self):
        now = time.time()
        for key, st in list(self._cloud_put_inflight.items()):
            try:
                ts = float(st.get("ts") or 0.0)
            except Exception:
                ts = 0.0
            if ts and (now - ts) > MAX_CLOUD_WAITER_AGE_SEC:
                self._cloud_put_inflight.pop(key, None)
                try:
                    self._log("WARN", "cloud_put_inflight.expired", owner=key[0], upload_id=key[1])
                except Exception:
                    pass
                try:
                    ws = st.get("ws")
                    blob_sha = st.get("blob_sha256") or ""
                    if ws:
                        asyncio.create_task(self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "TIMEOUT"}))
                except Exception:
                    pass

    def _cleanup_cloud_waiters(self):
        now = time.time()
        expired: list[tuple[str, dict]] = []
        for req_id, st in list(self._cloud_waiters.items()):
            try:
                ts = float(st.get("ts") or 0.0)
            except Exception:
                ts = 0.0
            if ts and (now - ts) > MAX_CLOUD_WAITER_AGE_SEC:
                expired.append((req_id, st))

        for req_id, st in expired:
            wsw = st.get("ws")
            self._cloud_waiters.pop(req_id, None)
            try:
                if wsw:

                    try:
                        sent = int(st.get("nodes_sent") or 0)
                    except Exception:
                        sent = 0
                    try:
                        responded = int(st.get("nodes_responded") or 0)
                    except Exception:
                        responded = 0
                    err = "NO_NODES" if sent <= 0 else "NOT_SEEDED"
                    try:
                        self._log(
                            "WARN",
                            "cloud_waiter.expired",
                            req_id=req_id,
                            requester=st.get("requester"),
                            kind=st.get("kind"),
                            fetch_mode=st.get("fetch_mode"),
                            blob_sha256=(str(st.get("blob_sha256"))[:12] if st.get("blob_sha256") else None),
                            file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                            nodes_sent=sent,
                            nodes_responded=responded,
                            error=err,
                        )
                    except Exception:
                        pass
                    asyncio.create_task(
                        self.send(
                            wsw,
                            {
                                "type": "cloud_blob",
                                "req_id": req_id,
                                "ok": False,
                                "error": err,
                                "nodes_sent": sent,
                                "nodes_responded": responded,
                            },
                        )
                    )
            except Exception:
                pass

    def _cleanup_meta_waiters(self):
        now = time.time()
        for req_id, st in list(self._meta_waiters.items()):
            try:
                ts = float(st.get("ts") or 0.0)
            except Exception:
                ts = 0.0
            if ts and (now - ts) > MAX_META_WAITER_AGE_SEC:
                self._meta_waiters.pop(req_id, None)
                fut = st.get("fut")
                if fut is not None and hasattr(fut, "done") and not fut.done():
                    try:
                        fut.set_result(st)
                    except Exception:
                        pass

    async def _cloud_try_next_node(self, req_id: str) -> bool:
        st = self._cloud_waiters.get(req_id)
        if not isinstance(st, dict):
            return False
        if st.get("chosen_node_id"):
            return False

        blob_sha = st.get("blob_sha256")
        if not isinstance(blob_sha, str) or not self._HEX64.match(blob_sha):
            return False

        queue = st.get("_node_try_queue")
        if not isinstance(queue, list) or not queue:
            return False
        try:
            idx = int(st.get("_node_try_idx") or 0)
        except Exception:
            idx = 0

        while idx < len(queue):
            nid = queue[idx]
            idx += 1
            st["_node_try_idx"] = int(idx)
            if not isinstance(nid, str) or not nid:
                continue
            if self.db.is_node_banned(nid):
                continue
            nws = self.nodes.get(nid)
            if not nws:
                continue

            ok = await self.send(nws, {"type": "node_fetch", "req_id": req_id, "blob_sha256": blob_sha})
            if ok:
                try:
                    st["nodes_sent"] = int(st.get("nodes_sent") or 0) + 1
                except Exception:
                    st["nodes_sent"] = 1
                try:
                    st["ts"] = time.time()
                except Exception:
                    pass
                return True

        return False

    async def _republish_db_snapshot_to_nodes(
        self,
        owner: str,
        blob_sha256: str,
        created_at: int,
        bytes_total: int,
        sig_b64: str,
        blob: bytes,
        skip_node_id: str | None = None,
        limit: int = 3,
    ) -> None:
        if not isinstance(owner, str) or not owner:
            return
        if not isinstance(blob_sha256, str) or not self._HEX64.match(blob_sha256):
            return
        if not isinstance(sig_b64, str) or not sig_b64:
            return
        if not isinstance(blob, (bytes, bytearray)) or not blob:
            return
        try:
            if len(blob) > MAX_CLOUD_DB_BYTES:
                return
        except Exception:
            return

        try:
            lim = int(limit)
        except Exception:
            lim = 3
        lim = max(0, min(lim, 10))
        if lim <= 0:
            return

        try:
            ct_b64 = b64e(bytes(blob))
        except Exception:
            return

        nodes = []
        now_t = time.time()
        for nid, nws in list(self.nodes.items()):
            if self.db.is_node_banned(nid):
                continue
            if skip_node_id and str(nid) == str(skip_node_id):
                continue
            nodes.append(str(nid))
        nodes.sort(key=lambda nid: self._node_score(nid, now=now_t), reverse=True)
        nodes = nodes[:lim]

        if not nodes:
            return

        msg = {
            "type": "node_store",
            "kind": "db",
            "owner": str(owner),
            "created_at": int(created_at),
            "blob_sha256": str(blob_sha256),
            "bytes_total": int(bytes_total),
            "ct": ct_b64,
            "sig": str(sig_b64),
        }

        for nid in nodes:
            nws = self.nodes.get(nid)
            if not nws:
                continue
            try:
                await self.send(nws, msg)
            except Exception:
                pass

    def _new_req_id(self, prefix: str = "m") -> str:
        try:
            return f"{prefix}_{os.urandom(12).hex()}"
        except Exception:
            return f"{prefix}_{int(time.time()*1000)}"

    def _verify_cloud_put_meta_sig(self, owner: str, meta: dict, sig_b64: str) -> bool:
        if not isinstance(owner, str) or not owner:
            return False
        if not isinstance(meta, dict):
            return False
        if not isinstance(sig_b64, str) or not sig_b64:
            return False
        try:
            acc = self.db.get_account(owner)
            if not acc:
                return False
            vk = VerifyKey(b64d(acc.sign_pub_b64))
            vk.verify(("cloud_put|" + canonical_json(meta)).encode(), b64d(sig_b64))
            return True
        except Exception:
            return False

    async def _collect_node_db_candidates(self, owner: str, limit: int = 10, max_nodes: int = 10, timeout: float = 2.0) -> dict:
        req_id = self._new_req_id("cand")
        fut = asyncio.get_running_loop().create_future()
        st = {
            "ts": time.time(),
            "fut": fut,
            "mode": "candidates",
            "owner": owner,
            "items": [],
            "nodes_sent": 0,
            "nodes_responded": 0,
            "target": int(max(1, min(int(max_nodes), 10))),
        }
        self._meta_waiters[req_id] = st

        sent = 0
        for nid, nws in list(self.nodes.items()):
            if self.db.is_node_banned(nid):
                continue
            try:
                if await self.send(nws, {"type": "node_db_candidates_req", "req_id": req_id, "owner": owner, "limit": int(limit)}):
                    sent += 1
            except Exception:
                pass
        st["nodes_sent"] = int(sent)
        if sent == 0:
            self._meta_waiters.pop(req_id, None)
            try:
                fut.set_result(st)
            except Exception:
                pass
            return st

        try:
            await asyncio.wait_for(fut, timeout=timeout)
        except Exception:
            pass
        out = self._meta_waiters.pop(req_id, st)
        return out

    async def _collect_node_db_has_newer(self, owner: str, since_created_at: int, max_nodes: int = 10, timeout: float = 2.0) -> dict:
        req_id = self._new_req_id("newer")
        fut = asyncio.get_running_loop().create_future()
        st = {
            "ts": time.time(),
            "fut": fut,
            "mode": "has_newer",
            "owner": owner,
            "items": [],
            "nodes_sent": 0,
            "nodes_responded": 0,
            "target": int(max(1, min(int(max_nodes), 10))),
        }
        self._meta_waiters[req_id] = st

        sent = 0
        for nid, nws in list(self.nodes.items()):
            if self.db.is_node_banned(nid):
                continue
            try:
                ok = await self.send(
                    nws,
                    {
                        "type": "node_db_has_newer_req",
                        "req_id": req_id,
                        "owner": owner,
                        "since_created_at": int(since_created_at),
                    },
                )
                if ok:
                    sent += 1
            except Exception:
                pass
        st["nodes_sent"] = int(sent)
        if sent == 0:
            self._meta_waiters.pop(req_id, None)
            try:
                fut.set_result(st)
            except Exception:
                pass
            return st

        try:
            await asyncio.wait_for(fut, timeout=timeout)
        except Exception:
            pass
        out = self._meta_waiters.pop(req_id, st)
        return out

    async def _cloud_get_db_verified(self, ws, owner: str, req_id: str):

        try:
            max_nodes = 10
            min_nodes = 1

            best = None
            best_is_server_index = False
            try:
                for it in self.db.list_cloud_blobs(owner, "db", limit=10) or []:
                    if not isinstance(it, dict):
                        continue
                    try:
                        blob_sha = str(it.get("blob_sha256") or "")
                        created_at = int(it.get("created_at") or 0)
                        bytes_total = int(it.get("bytes_total") or 0)
                        sig_b64 = str(it.get("sig_b64") or "")
                    except Exception:
                        continue
                    if not blob_sha or not self._HEX64.match(blob_sha) or created_at <= 0 or bytes_total <= 0:
                        continue
                    if (best is None) or int(created_at) > int(best.get("created_at") or 0):
                        best = {"blob_sha256": blob_sha, "created_at": int(created_at), "bytes_total": int(bytes_total), "sig_b64": sig_b64}
                        best_is_server_index = True
            except Exception:
                pass

            cand = await self._collect_node_db_candidates(owner, limit=10, max_nodes=max_nodes, timeout=12.0)
            sent = int(cand.get("nodes_sent") or 0)
            responded = int(cand.get("nodes_responded") or 0)
            if sent == 0:
                await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NO_NODES", "nodes_sent": 0, "nodes_responded": 0})
                return

            sha_to_nodes: dict[str, set[str]] = {}
            for it in cand.get("items") or []:
                if not isinstance(it, dict):
                    continue
                try:
                    blob_sha = str(it.get("blob_sha256") or "")
                    created_at = int(it.get("created_at") or 0)
                    bytes_total = int(it.get("bytes_total") or 0)
                    sig_b64 = str(it.get("sig") or it.get("sig_b64") or "")
                    nid = str(it.get("node_id") or "")
                except Exception:
                    continue
                if not blob_sha or not self._HEX64.match(blob_sha) or created_at <= 0 or bytes_total <= 0:
                    continue
                if isinstance(nid, str) and nid:
                    sha_to_nodes.setdefault(blob_sha, set()).add(nid)
                meta = {
                    "owner": owner,
                    "kind": "db",
                    "created_at": int(created_at),
                    "blob_sha256": blob_sha,
                    "bytes_total": int(bytes_total),
                }
                if not self._verify_cloud_put_meta_sig(owner, meta, sig_b64):
                    continue
                if (best is None) or int(created_at) > int(best.get("created_at") or 0):
                    best = {"blob_sha256": blob_sha, "created_at": int(created_at), "bytes_total": int(bytes_total), "sig_b64": sig_b64}
                    best_is_server_index = False

            if not best:

                await self.send(
                    ws,
                    {
                        "type": "cloud_blob",
                        "req_id": req_id,
                        "ok": False,
                        "error": "NOT_SEEDED",
                        "nodes_sent": sent,
                        "nodes_responded": responded,
                    },
                )
                return

            if (not best_is_server_index) and min_nodes > 0 and responded < min_nodes:
                await self.send(
                    ws,
                    {
                        "type": "cloud_blob",
                        "req_id": req_id,
                        "ok": False,
                        "error": "TIMEOUT",
                        "nodes_sent": sent,
                        "nodes_responded": responded,
                    },
                )
                return

            for _ in range(5):
                newer = await self._collect_node_db_has_newer(owner, int(best["created_at"]), max_nodes=max_nodes, timeout=3.0)
                any_new = None
                for it in newer.get("items") or []:
                    if not isinstance(it, dict):
                        continue
                    try:
                        blob_sha = str(it.get("blob_sha256") or "")
                        created_at = int(it.get("created_at") or 0)
                        bytes_total = int(it.get("bytes_total") or 0)
                        sig_b64 = str(it.get("sig") or it.get("sig_b64") or "")
                        nid = str(it.get("node_id") or "")
                    except Exception:
                        continue
                    if created_at <= int(best["created_at"]):
                        continue
                    if not blob_sha or not self._HEX64.match(blob_sha) or bytes_total <= 0:
                        continue
                    if isinstance(nid, str) and nid:
                        sha_to_nodes.setdefault(blob_sha, set()).add(nid)
                    meta = {
                        "owner": owner,
                        "kind": "db",
                        "created_at": int(created_at),
                        "blob_sha256": blob_sha,
                        "bytes_total": int(bytes_total),
                    }
                    if not self._verify_cloud_put_meta_sig(owner, meta, sig_b64):
                        continue
                    if (any_new is None) or int(created_at) > int(any_new.get("created_at") or 0):
                        any_new = {"blob_sha256": blob_sha, "created_at": int(created_at), "bytes_total": int(bytes_total), "sig_b64": sig_b64}

                if not any_new:
                    break
                best = any_new

            try:
                self.db.add_cloud_index(owner, "db", best["blob_sha256"], int(best["bytes_total"]), int(best["created_at"]), str(best.get("sig_b64") or ""))
            except Exception:
                pass

            best_sha = str(best["blob_sha256"])
            try:
                node_set = sha_to_nodes.get(best_sha) or set()
            except Exception:
                node_set = set()
            node_list = [str(n) for n in node_set if isinstance(n, str) and n]
            if not node_list:

                node_list = [str(nid) for nid in list(self.nodes.keys()) if isinstance(nid, str) and nid and not self.db.is_node_banned(nid)]
            now_t = time.time()
            node_list.sort(key=lambda nid: self._node_score(nid, now=now_t), reverse=True)

            self._cloud_waiters[req_id] = {
                "ws": ws,
                "blob_sha256": best_sha,
                "owner": owner,
                "kind": "db",
                "bytes_total": int(best["bytes_total"]),
                "created_at": int(best["created_at"]),
                "sig_b64": str(best.get("sig_b64") or ""),
                "ts": time.time(),
                "nodes_sent": 0,
                "nodes_responded": 0,
                "_node_try_queue": node_list,
                "_node_try_idx": 0,
                "_republish_db": True,
            }

            if not await self._cloud_try_next_node(req_id):
                self._cloud_waiters.pop(req_id, None)
                await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NO_NODES", "nodes_sent": 0, "nodes_responded": 0})
        except Exception:
            try:
                await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "TIMEOUT"})
            except Exception:
                pass

    async def housekeeping_loop(self, interval_sec: float = 1.0) -> None:
        try:
            interval = float(interval_sec or 1.0)
        except Exception:
            interval = 1.0
        interval = max(0.25, min(interval, 5.0))
        while True:
            try:
                self._cleanup_cloud_waiters()
            except Exception:
                pass
            try:
                self._cleanup_cloud_put_inflight()
            except Exception:
                pass
            try:
                self._cleanup_meta_waiters()
            except Exception:
                pass
            await asyncio.sleep(interval)

    async def _distribute_to_nodes(
        self,
        owner: str,
        kind: str,
        created_at: int,
        blob_sha: str,
        file_id: str,
        bytes_total: int,
        nonce: str,
        buf: bytes,
        sig: str,
        owner_sign_pub: str,
    ) -> None:
        CHUNK_SIZE = 4 * 1024 * 1024

        for nid, nws in list(self.nodes.items()):
            if self.db.is_node_banned(nid):
                continue
            try:
                if bytes_total <= CHUNK_SIZE:
                    node_payload = {
                        "type": "node_store",
                        "owner": owner,
                        "kind": kind,
                        "created_at": created_at,
                        "blob_sha256": blob_sha,
                        "file_id": file_id,
                        "bytes_total": bytes_total,
                        "nonce": nonce,
                        "ct": b64e(buf),
                        "sig": sig,
                        "owner_sign_pub": owner_sign_pub,
                    }
                    await self.send(nws, node_payload)
                else:
                    import uuid
                    upload_id = uuid.uuid4().hex
                    await self.send(nws, {
                        "type": "node_store_begin",
                        "upload_id": upload_id,
                        "owner": owner,
                        "kind": kind,
                        "created_at": created_at,
                        "blob_sha256": blob_sha,
                        "file_id": file_id,
                        "bytes_total": bytes_total,
                        "nonce": nonce,
                        "sig": sig,
                        "owner_sign_pub": owner_sign_pub,
                    })
                    offset = 0
                    idx = 0
                    while offset < len(buf):
                        chunk = buf[offset:offset + CHUNK_SIZE]
                        await self.send(nws, {
                            "type": "node_store_chunk",
                            "upload_id": upload_id,
                            "idx": idx,
                            "data": b64e(chunk),
                        })
                        offset += len(chunk)
                        idx += 1
                    await self.send(nws, {
                        "type": "node_store_end",
                        "upload_id": upload_id,
                    })
            except Exception:
                pass

    async def _fail_cloud_waiter_if_exhausted(self, req_id: str, error: str = "NOT_SEEDED") -> None:
        st = self._cloud_waiters.get(req_id)
        if not isinstance(st, dict):
            return
        if st.get("chosen_node_id"):
            return
        try:
            sent = int(st.get("nodes_sent") or 0)
            responded = int(st.get("nodes_responded") or 0)
        except Exception:
            return
        if sent > 0 and responded >= sent:
            wsw = st.get("ws")
            self._cloud_waiters.pop(req_id, None)
            try:
                self._log(
                    "INFO",
                    "cloud_waiter.exhausted",
                    req_id=req_id,
                    requester=st.get("requester"),
                    kind=st.get("kind"),
                    fetch_mode=st.get("fetch_mode"),
                    blob_sha256=(str(st.get("blob_sha256"))[:12] if st.get("blob_sha256") else None),
                    file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                    nodes_sent=sent,
                    nodes_responded=responded,
                    error=str(error or "NOT_SEEDED"),
                )
            except Exception:
                pass
            try:
                if wsw:
                    await self.send(
                        wsw,
                        {
                            "type": "cloud_blob",
                            "req_id": req_id,
                            "ok": False,
                            "error": str(error or "NOT_SEEDED"),
                            "nodes_sent": sent,
                            "nodes_responded": responded,
                        },
                    )
            except Exception:
                pass

    async def send(self, ws, obj: dict) -> bool:
        try:

            t = obj.get("type") if isinstance(obj, dict) else None
            if t in ("node_fetch", "node_fetch_file"):
                nid = self._ws_node_id.get(ws)
                req_id = obj.get("req_id") if isinstance(obj, dict) else None
                if isinstance(nid, str) and isinstance(req_id, str):
                    st = self._cloud_waiters.get(req_id)
                    if isinstance(st, dict):
                        mp = st.get("_node_send_ts")
                        if not isinstance(mp, dict):
                            mp = {}
                            st["_node_send_ts"] = mp
                        mp[str(nid)] = float(time.time())
        except Exception:
            pass
        try:
            await ws.send(canonical_json(obj))
            return True
        except Exception:
            return False

    async def deliver_or_queue(self, to_alias: str, payload: dict):
        ws = self.online.get(to_alias)
        if ws:
            await self.send(ws, payload)
            return

        try:
            if isinstance(payload, dict) and payload.get("type") == "voice_frame":
                return
        except Exception:
            return

        self.db.queue_payload(to_alias, payload)

    async def handler(self, ws, path=None):

        ip = ws.remote_address[0]

        if hasattr(ws, "request_headers"):
            if "X-Forwarded-For" in ws.request_headers:
                ip = ws.request_headers["X-Forwarded-For"].split(",")[0].strip()
            elif "X-Real-IP" in ws.request_headers:
                ip = ws.request_headers["X-Real-IP"]

        masked_ip = "HIDDEN"
        if "." in ip:
            parts = ip.split(".")
            if len(parts) == 4:
                masked_ip = f"{parts[0]}.{parts[1]}.*.*"
        elif ":" in ip:
            masked_ip = "IPv6_HIDDEN"

        if self.ip_counts.get(ip, 0) >= self.MAX_CONNS_PER_IP:
            print(f"[{masked_ip}] Connection rejected: Too many connections")
            await ws.close(1008, "Too many connections")
            return
        self.ip_counts[ip] = self.ip_counts.get(ip, 0) + 1

        authed_alias = None
        node_id = None
        pending_node = None
        msg_bucket = 0
        last_msg_time = time.time()

        def _allow_node_register() -> bool:
            try:
                v = str(os.getenv("ANTCH_ALLOW_NODE_REGISTER") or "").strip().lower()
            except Exception:
                v = ""

            return v not in ("0", "false", "no", "off")

        def _valid_node_id(nid: str) -> bool:
            if not isinstance(nid, str):
                return False
            s = nid.strip()
            if not s or len(s) > MAX_NODE_ID_LEN:
                return False
            return True

        try:
            async for raw in ws:
                try:
                    self._cleanup_cloud_waiters()
                except Exception:
                    pass
                try:
                    self._cleanup_cloud_put_inflight()
                except Exception:
                    pass
                try:
                    self._cleanup_meta_waiters()
                except Exception:
                    pass

                now = time.time()
                if now - last_msg_time > 1.0:
                    msg_bucket = 0
                    last_msg_time = now
                msg_bucket += 1
                if msg_bucket > self.MAX_MSGS_PER_SEC:
                    await ws.close(1008, "Rate limit exceeded")
                    return

                try: msg = json.loads(raw)
                except: continue
                t = msg.get("type")

                if t == "node_hello":
                    nid = msg.get("node_id")
                    if not isinstance(nid, str) or not _valid_node_id(nid):
                        continue
                    if self.db.is_node_banned(nid):
                        await ws.close(1008, "banned")
                        return

                    desc = msg.get("description")
                    if isinstance(desc, str):
                        desc = desc.strip()
                        if len(desc) > MAX_NODE_DESC_LEN:
                            desc = desc[:MAX_NODE_DESC_LEN]
                    else:
                        desc = ""

                    caps = msg.get("caps")
                    if not isinstance(caps, dict):
                        caps = {}

                    nonce_b = os.urandom(24)
                    nonce_b64 = b64e(nonce_b)
                    srv_ts = now_ts()
                    pending_node = {
                        "node_id": str(nid),
                        "description": str(desc),
                        "caps": dict(caps),
                        "nonce": str(nonce_b64),
                        "ts": int(srv_ts),
                        "created": float(time.time()),
                    }

                    await self.send(ws, {"type": "node_challenge", "node_id": str(nid), "nonce": nonce_b64, "ts": int(srv_ts)})
                    continue

                if t == "node_auth":
                    if node_id:
                        continue
                    if not isinstance(pending_node, dict):
                        continue

                    nid = msg.get("node_id")
                    nonce = msg.get("nonce")
                    sig_b64 = msg.get("sig")
                    sign_pub_b64 = msg.get("sign_pub")
                    srv_ts = msg.get("ts")

                    if not isinstance(nid, str) or str(nid) != str(pending_node.get("node_id")):
                        continue
                    if not isinstance(nonce, str) or str(nonce) != str(pending_node.get("nonce")):
                        continue
                    if not isinstance(sig_b64, str) or not sig_b64:
                        await ws.close(1008, "node_auth_required")
                        return
                    if not isinstance(sign_pub_b64, str) or not sign_pub_b64 or len(sign_pub_b64) > 256:
                        await ws.close(1008, "bad_node_key")
                        return
                    try:
                        srv_ts_i = int(srv_ts)
                    except Exception:
                        srv_ts_i = int(pending_node.get("ts") or 0)
                    if srv_ts_i != int(pending_node.get("ts") or 0):
                        await ws.close(1008, "bad_challenge")
                        return

                    stored_pub = self.db.get_node_sign_pub_b64(str(nid))
                    if stored_pub and str(stored_pub) != str(sign_pub_b64):
                        await ws.close(1008, "node_key_mismatch")
                        return

                    payload = {
                        "node_id": str(nid),
                        "nonce": str(nonce),
                        "ts": int(srv_ts_i),
                    }
                    to_verify = ("node_auth|" + canonical_json(payload)).encode("utf-8")
                    try:
                        VerifyKey(b64d(str(sign_pub_b64))).verify(to_verify, b64d(str(sig_b64)))
                    except Exception:
                        await ws.close(1008, "bad_node_sig")
                        return

                    if not stored_pub:
                        if _allow_node_register():
                            self.db.upsert_node_sign_pub_b64(str(nid), str(sign_pub_b64))
                            try:
                                self._log("INFO", "node.registered", node_id=str(nid), ip=str(masked_ip))
                            except Exception:
                                pass
                        else:
                            await ws.close(1008, "node_not_registered")
                            return

                    node_id = str(nid)
                    self.nodes[node_id] = ws
                    self._ws_node_id[ws] = node_id
                    self.node_caps[node_id] = dict(pending_node.get("caps") or {})

                    stn = self._node_ensure(node_id)
                    stn["connected"] = True
                    stn["connected_at"] = float(time.time())
                    stn["last_seen"] = float(time.time())
                    stn["description"] = str(pending_node.get("description") or "")
                    stn["sign_pub_b64"] = str(sign_pub_b64)

                    pending_node = None

                    try:
                        self._log(
                            "INFO",
                            "node.connected",
                            node_id=str(node_id),
                            ip=str(masked_ip),
                            caps=self.node_caps.get(node_id),
                            nodes_total=len(self.nodes),
                        )
                    except Exception:
                        pass
                    await self.send(ws, {"type": "node_ok"})
                    continue

                if t == "node_alive":
                    if node_id:
                        self._node_touch(str(node_id))
                    continue

                if t == "register":
                    alias, fp, sp, dp = msg.get("alias"), msg.get("fp"), msg.get("sign_pub"), msg.get("dh_pub")
                    if not all([alias, fp, sp, dp]): continue
                    existing = self.db.get_account(alias)
                    if existing and existing.sign_pub_b64 != sp:
                        await self.send(ws, {"type": "error", "error": "ALIAS_TAKEN"})
                        continue
                    chall = os.urandom(32)
                    self.challenges[ws] = {"alias": alias, "fp": fp, "sp": sp, "dp": dp, "ch": chall}
                    await self.send(ws, {"type": "challenge", "challenge": b64e(chall)})
                elif t == "auth":
                    st = self.challenges.get(ws)
                    if not st: continue
                    sig = msg.get("sig")
                    if not sig: continue
                    try: VerifyKey(b64d(st["sp"])).verify(st["ch"], b64d(sig))
                    except BadSignatureError: continue
                    self.db.upsert_account(st["alias"], st["fp"], st["sp"], st["dp"])
                    self.online[st["alias"]] = ws
                    self.last_seen[st["alias"]] = time.time()
                    authed_alias = st["alias"]
                    acc = self.db.get_account(st["alias"])
                    prof = self.db.get_profile(st["alias"])

                    profile_data = {
                        "status": prof[0] if prof else "online",
                        "status_msg": prof[1] if prof else "",
                        "bio": prof[2] if prof else "",
                        "visible": prof[3] if prof else 1
                    }
                    blocked = []
                    try:
                        blocked = self.db.list_blocked(st["alias"])
                    except Exception:
                        blocked = []
                    await self.send(ws, {"type": "ok", "msg": "AUTH_OK", "account": {"alias": st["alias"], "fp": st["fp"], "created_at": acc.created_at if acc else now_ts(), "updated_at": acc.updated_at if acc else now_ts()}, "profile": profile_data, "blocked": blocked})
                    for p in self.db.pop_offline(st["alias"]): await self.send(ws, p)

                elif (not authed_alias) and (not node_id):
                    await self.send(ws, {"type": "error", "error": "NOT_AUTHED"})
                elif t == "alive":
                    if authed_alias:

                        self.online[authed_alias] = ws
                        self.last_seen[authed_alias] = time.time()
                elif t == "list":

                    items = []

                    to_remove = []
                    now = time.time()
                    for a, last_t in self.last_seen.items():
                        if now - last_t > 35:
                            to_remove.append(a)
                    for a in to_remove:
                        self.online.pop(a, None)
                        self.last_seen.pop(a, None)

                    all_users = self.db.get_all_users()
                    for row in all_users:

                        alias, fp, db_status, status_msg, bio, visible = row

                        if visible == 0: continue

                        is_alive = (alias in self.online)

                        final_status = db_status if is_alive else "offline"

                        items.append({
                            "alias": alias,
                            "fp": fp,
                            "status": final_status,
                            "status_msg": status_msg or "",
                            "bio": bio or ""
                        })

                    await self.send(ws, {"type": "list", "items": items})
                elif t == "update_profile":
                    if not authed_alias: continue

                    current_prof = self.db.get_profile(authed_alias)

                    if current_prof:
                        cur_status, cur_msg, cur_bio, cur_vis = current_prof
                    else:
                        cur_status, cur_msg, cur_bio, cur_vis = ("online", "", "", 1)

                    new_status = msg.get("status")
                    new_msg = msg.get("status_msg")
                    new_bio = msg.get("bio")

                    vis_val = msg.get("visible")
                    if vis_val is None: vis_val = msg.get("visibility")

                    final_status = new_status if new_status is not None else cur_status
                    final_msg = new_msg if new_msg is not None else cur_msg
                    final_bio = new_bio if new_bio is not None else cur_bio

                    if vis_val is not None:
                        final_vis = 1 if vis_val else 0
                    else:
                        final_vis = cur_vis

                    self.db.update_profile(
                        authed_alias,
                        final_status,
                        final_msg,
                        final_bio,
                        final_vis
                    )

                elif t == "block":
                    if not authed_alias: continue
                    raw = msg.get("target") or msg.get("alias") or msg.get("fp")
                    target = None
                    if isinstance(raw, str) and raw:
                        raw = raw.strip()
                        target = raw
                        if "-" in raw:
                            a = self.db.get_alias_by_fp(raw)
                            if a:
                                target = a
                    if target:
                        self.db.block_user(authed_alias, target)
                    try:
                        await self.send(ws, {"type": "blocks", "items": self.db.list_blocked(authed_alias)})
                    except Exception:
                        pass
                elif t == "unblock":
                    if not authed_alias: continue
                    raw = msg.get("target") or msg.get("alias") or msg.get("fp")
                    target = None
                    if isinstance(raw, str) and raw:
                        raw = raw.strip()
                        target = raw
                        if "-" in raw:
                            a = self.db.get_alias_by_fp(raw)
                            if a:
                                target = a
                    if target:
                        self.db.unblock_user(authed_alias, target)
                    try:
                        await self.send(ws, {"type": "blocks", "items": self.db.list_blocked(authed_alias)})
                    except Exception:
                        pass
                elif t == "delete_account":
                    if not authed_alias: continue

                    try:
                        items = self.db.list_cloud_blobs(authed_alias, "db", limit=5000)
                    except Exception:
                        items = []

                    if isinstance(items, list) and items:
                        for it in items:
                            try:
                                sha = it.get("blob_sha256") if isinstance(it, dict) else None
                            except Exception:
                                sha = None
                            if not isinstance(sha, str) or not self._HEX64.match(sha):
                                continue
                            try:
                                self.db.delete_cloud_index(authed_alias, "db", sha)
                            except Exception:
                                pass
                            node_payload = {"type": "node_delete", "owner": authed_alias, "kind": "db", "blob_sha256": sha}
                            for nid, nws in list(self.nodes.items()):
                                if self.db.is_node_banned(nid):
                                    continue
                                try:
                                    await self.send(nws, node_payload)
                                except Exception:
                                    pass

                    self.db.delete_account(authed_alias)
                    await self.send(ws, {"type": "delete_account", "ok": True})
                    return

                elif t == "cloud_put_begin":
                    if not authed_alias:
                        continue
                    owner = msg.get("owner")
                    kind = msg.get("kind")
                    created_at = msg.get("created_at")
                    blob_sha = msg.get("blob_sha256")
                    bytes_total = msg.get("bytes_total")
                    nonce = msg.get("nonce")
                    sig = msg.get("sig")
                    file_id = msg.get("file_id")
                    upload_id = msg.get("upload_id")
                    if owner != authed_alias:
                        continue
                    if not isinstance(kind, str) or kind not in ("db", "file"):
                        continue
                    if not isinstance(upload_id, str) or not upload_id or len(upload_id) > 96:
                        continue
                    if not isinstance(blob_sha, str) or not self._HEX64.match(blob_sha):
                        continue
                    try:
                        bytes_total_i = int(bytes_total)
                    except Exception:
                        continue
                    if bytes_total_i <= 0:
                        continue
                    if not isinstance(created_at, int):
                        created_at = now_ts()
                    if not isinstance(nonce, str):
                        nonce = ""
                    if len(nonce) > MAX_NONCE_LEN:
                        continue
                    if not isinstance(sig, str) or not sig:
                        continue

                    sig_raw = _decode_b64_sig_ed25519(sig)
                    if sig_raw is None:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "BAD_SIG"})
                        continue

                    if kind == "file":
                        if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                            continue
                    else:
                        file_id = ""

                    lim = MAX_CLOUD_DB_BYTES if kind == "db" else MAX_CLOUD_FILE_BYTES
                    if bytes_total_i > lim:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "TOO_LARGE"})
                        continue

                    meta = {
                        "owner": owner,
                        "kind": kind,
                        "created_at": int(created_at),
                        "blob_sha256": blob_sha,
                        "bytes_total": int(bytes_total_i),
                    }
                    if kind == "file":
                        meta["file_id"] = str(file_id)
                    try:
                        acc = self.db.get_account(owner)
                        if not acc:
                            continue
                        vk = VerifyKey(b64d(acc.sign_pub_b64))
                        vk.verify(("cloud_put|" + canonical_json(meta)).encode(), sig_raw)
                    except Exception:
                        continue

                    key = (owner, upload_id)
                    if key in self._cloud_put_inflight:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "DUPLICATE"})
                        continue

                    self._cloud_put_inflight[key] = {
                        "ws": ws,
                        "ts": time.time(),
                        "owner": owner,
                        "kind": kind,
                        "created_at": int(created_at),
                        "blob_sha256": blob_sha,
                        "bytes_total": int(bytes_total_i),
                        "nonce": nonce,
                        "sig": sig,
                        "file_id": str(file_id or ""),
                        "buf": bytearray(int(bytes_total_i)),
                        "offset": 0,
                        "next_idx": 0,
                    }
                    continue

                elif t == "cloud_put_chunk":
                    if not authed_alias:
                        continue
                    upload_id = msg.get("upload_id")
                    idx = msg.get("idx")
                    data_b64 = msg.get("data")
                    if not isinstance(upload_id, str) or not upload_id or len(upload_id) > 96:
                        continue
                    if not isinstance(idx, int) or idx < 0:
                        continue
                    if not isinstance(data_b64, str) or not data_b64 or len(data_b64) > MAX_CLOUD_CHUNK_B64:
                        continue
                    key = (authed_alias, upload_id)
                    st = self._cloud_put_inflight.get(key)
                    if not isinstance(st, dict):
                        continue
                    try:
                        if int(st.get("next_idx") or 0) != int(idx):
                            await self.send(ws, {"type": "cloud_put_err", "blob_sha256": st.get("blob_sha256") or "", "error": "BAD_IDX"})
                            self._cloud_put_inflight.pop(key, None)
                            continue
                    except Exception:
                        self._cloud_put_inflight.pop(key, None)
                        continue

                    try:
                        chunk = b64d(data_b64)
                    except Exception:
                        chunk = b""
                    if not chunk:
                        continue

                    try:
                        off = int(st.get("offset") or 0)
                        total = int(st.get("bytes_total") or 0)
                    except Exception:
                        self._cloud_put_inflight.pop(key, None)
                        continue
                    if off < 0 or total <= 0 or (off + len(chunk)) > total:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": st.get("blob_sha256") or "", "error": "TOO_LARGE"})
                        self._cloud_put_inflight.pop(key, None)
                        continue

                    buf = st.get("buf")
                    if not isinstance(buf, (bytearray, memoryview)):
                        self._cloud_put_inflight.pop(key, None)
                        continue
                    try:
                        buf[off : off + len(chunk)] = chunk
                    except Exception:
                        self._cloud_put_inflight.pop(key, None)
                        continue
                    st["offset"] = off + len(chunk)
                    st["next_idx"] = int(idx) + 1
                    st["ts"] = time.time()
                    continue

                elif t == "cloud_put_end":
                    if not authed_alias:
                        continue
                    upload_id = msg.get("upload_id")
                    if not isinstance(upload_id, str) or not upload_id or len(upload_id) > 96:
                        continue
                    key = (authed_alias, upload_id)
                    st = self._cloud_put_inflight.pop(key, None)
                    if not isinstance(st, dict):
                        continue

                    try:
                        owner = str(st.get("owner") or "")
                        kind = str(st.get("kind") or "")
                        created_at = int(st.get("created_at") or now_ts())
                        blob_sha = str(st.get("blob_sha256") or "")
                        bytes_total_i = int(st.get("bytes_total") or 0)
                        nonce = str(st.get("nonce") or "")
                        sig = str(st.get("sig") or "")
                        file_id = str(st.get("file_id") or "")
                        buf = st.get("buf")
                        off = int(st.get("offset") or 0)
                    except Exception:
                        continue

                    if owner != authed_alias:
                        continue
                    if kind not in ("db", "file"):
                        continue
                    if kind == "file" and not self._HEX64.match(file_id or ""):
                        continue
                    if not self._HEX64.match(blob_sha):
                        continue
                    if bytes_total_i <= 0 or off != bytes_total_i:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "INCOMPLETE"})
                        continue

                    lim = MAX_CLOUD_DB_BYTES if kind == "db" else MAX_CLOUD_FILE_BYTES
                    if bytes_total_i > lim:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "TOO_LARGE"})
                        continue

                    try:
                        h = hashlib.sha256()
                        h.update(buf)
                        got_sha = h.hexdigest()
                    except Exception:
                        continue
                    if got_sha != blob_sha:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "SHA_MISMATCH"})
                        continue

                    sig_raw = _decode_b64_sig_ed25519(sig)
                    if sig_raw is None:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "BAD_SIG"})
                        continue

                    if kind == "file":
                        derived = compute_file_id_from_container(bytes(buf))
                        if not isinstance(derived, str) or not self._HEX64.match(derived):
                            await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "BAD_FILE_CONTAINER"})
                            continue
                        if str(file_id) != str(derived):
                            await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "FILE_ID_MISMATCH"})
                            continue

                    self.db.add_cloud_index(owner, kind, blob_sha, bytes_total_i, int(created_at), sig, file_id=file_id)

                    await self.send(ws, {"type": "cloud_put_ok", "blob_sha256": blob_sha})

                    owner_sign_pub = ""
                    try:
                        acc = self.db.get_account(owner)
                        owner_sign_pub = str(acc.sign_pub_b64) if acc else ""
                    except Exception:
                        owner_sign_pub = ""

                    asyncio.create_task(self._distribute_to_nodes(
                        owner=owner,
                        kind=kind,
                        created_at=int(created_at),
                        blob_sha=blob_sha,
                        file_id=file_id,
                        bytes_total=int(bytes_total_i),
                        nonce=nonce,
                        buf=bytes(buf),
                        sig=sig,
                        owner_sign_pub=owner_sign_pub,
                    ))
                    continue
                elif t == "lookup":
                    acc = self.db.get_account(msg.get("alias"))
                    if acc:
                        prof = self.db.get_profile(acc.alias)
                        await self.send(ws, {
                            "type": "lookup", "ok": True,
                            "alias": acc.alias, "fp": acc.fp,
                            "sign_pub": acc.sign_pub_b64, "dh_pub": acc.dh_pub_b64,
                            "created_at": acc.created_at, "updated_at": acc.updated_at,
                            "status": prof[0] if prof else "online",
                            "status_msg": prof[1] if prof else "",
                            "bio": prof[2] if prof else ""
                        })
                    else: await self.send(ws, {"type": "lookup", "ok": False})
                elif t == "relay":
                    to, pl = msg.get("to"), msg.get("payload")
                    if to and pl:

                        if authed_alias and self.db.is_blocked(to, authed_alias):

                            continue
                        await self.deliver_or_queue(to, pl)

                elif t == "group_call_join":
                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    call_id = msg.get("call_id")
                    if not isinstance(group_id, str) or not group_id:
                        continue
                    if not isinstance(call_id, str) or not call_id:
                        continue

                    gc = self.group_calls.get(group_id)
                    if gc is None:

                        gc = {
                            "call_id": call_id,
                            "participants": set(),
                            "started_at": time.time(),
                        }
                        self.group_calls[group_id] = gc
                    else:

                        call_id = gc["call_id"]

                    gc["participants"].add(authed_alias)
                    self._alias_in_group_call[authed_alias] = group_id

                    join_msg = {
                        "type": "group_call_state",
                        "group_id": group_id,
                        "call_id": gc["call_id"],
                        "participants": list(gc["participants"]),
                        "event": "join",
                        "who": authed_alias,
                    }
                    tasks = [self.send(pws, join_msg) for p in gc["participants"] if (pws := self.online.get(p))]
                    if tasks:
                        await asyncio.gather(*tasks, return_exceptions=True)

                elif t == "group_call_leave":
                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    if not isinstance(group_id, str) or not group_id:

                        group_id = self._alias_in_group_call.get(authed_alias)
                    if not group_id:
                        continue

                    gc = self.group_calls.get(group_id)
                    if gc is None:
                        continue

                    gc["participants"].discard(authed_alias)
                    self._alias_in_group_call.pop(authed_alias, None)

                    leave_msg = {
                        "type": "group_call_state",
                        "group_id": group_id,
                        "call_id": gc["call_id"],
                        "participants": list(gc["participants"]),
                        "event": "leave",
                        "who": authed_alias,
                    }
                    tasks = [self.send(pws, leave_msg) for p in gc["participants"] if (pws := self.online.get(p))]
                    if tasks:
                        await asyncio.gather(*tasks, return_exceptions=True)

                    if not gc["participants"]:
                        del self.group_calls[group_id]

                elif t == "group_call_ring":

                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    target = msg.get("target")
                    if not isinstance(group_id, str) or not group_id:
                        continue

                    gc = self.group_calls.get(group_id)
                    call_id = gc["call_id"] if gc else msg.get("call_id") or ""

                    members = msg.get("members")
                    if not isinstance(members, list):
                        members = []

                    targets = []
                    if isinstance(target, str) and target:
                        targets = [target]
                    else:
                        targets = [m for m in members if m != authed_alias]

                    for t_alias in targets:
                        if not isinstance(t_alias, str) or not t_alias:
                            continue
                        if self.db.is_blocked(t_alias, authed_alias):
                            continue
                        tws = self.online.get(t_alias)
                        if tws:
                            await self.send(tws, {
                                "type": "group_call_ring",
                                "group_id": group_id,
                                "call_id": call_id,
                                "from": authed_alias,
                                "members": members,
                            })

                elif t == "group_voice_frame":

                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    if not isinstance(group_id, str) or not group_id:
                        continue

                    gc = self.group_calls.get(group_id)
                    if gc is None:
                        continue
                    if authed_alias not in gc["participants"]:
                        continue

                    frame_data = {
                        "type": "group_voice_frame",
                        "group_id": group_id,
                        "call_id": gc["call_id"],
                        "from": authed_alias,
                        "seq": msg.get("seq"),
                        "nonce": msg.get("nonce"),
                        "ct": msg.get("ct"),
                    }
                    tasks = []
                    for p in gc["participants"]:
                        if p == authed_alias:
                            continue
                        pws = self.online.get(p)
                        if pws:
                            tasks.append(self.send(pws, frame_data))
                    if tasks:
                        await asyncio.gather(*tasks, return_exceptions=True)

                elif t == "group_call_query":

                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    if not isinstance(group_id, str) or not group_id:
                        continue

                    gc = self.group_calls.get(group_id)
                    if gc:
                        await self.send(ws, {
                            "type": "group_call_state",
                            "group_id": group_id,
                            "call_id": gc["call_id"],
                            "participants": list(gc["participants"]),
                            "event": "query",
                        })
                    else:
                        await self.send(ws, {
                            "type": "group_call_state",
                            "group_id": group_id,
                            "call_id": "",
                            "participants": [],
                            "event": "query",
                        })

                elif t == "group_member_ring":

                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    targets = msg.get("targets", [])
                    if not isinstance(group_id, str) or not group_id:
                        continue
                    if not isinstance(targets, list):
                        continue

                    g = self.db.get_group(group_id)
                    if not g:
                        continue
                    if authed_alias not in g.get("members", []):
                        continue

                    gc = self.group_calls.get(group_id)
                    call_id = gc["call_id"] if gc else ""

                    for t_alias in targets:
                        if not isinstance(t_alias, str) or not t_alias:
                            continue
                        if t_alias not in g.get("members", []):
                            continue
                        if self.db.is_blocked(t_alias, authed_alias):
                            continue
                        tws = self.online.get(t_alias)
                        if tws:
                            await self.send(tws, {
                                "type": "group_call_ring",
                                "group_id": group_id,
                                "call_id": call_id,
                                "from": authed_alias,
                                "members": g.get("members", []),
                            })

                elif t == "group_kick":

                    if not authed_alias:
                        continue
                    group_id = msg.get("group_id")
                    target = msg.get("target")
                    if not isinstance(group_id, str) or not group_id:
                        continue
                    if not isinstance(target, str) or not target:
                        continue

                    g = self.db.get_group(group_id)
                    if not g:
                        continue

                    if g.get("creator") != authed_alias:
                        await self.send(ws, {
                            "type": "error",
                            "msg": "Only group creator can kick members",
                        })
                        continue

                    if target == authed_alias:
                        continue

                    members = g.get("members", [])
                    if target in members:
                        members.remove(target)
                        g["members"] = members
                        self.db.save_group(g)

                        tws = self.online.get(target)
                        if tws:
                            await self.send(tws, {
                                "type": "group_kicked",
                                "group_id": group_id,
                                "by": authed_alias,
                            })

                        for m in members:
                            mws = self.online.get(m)
                            if mws:
                                await self.send(mws, {
                                    "type": "group_member_left",
                                    "group_id": group_id,
                                    "alias": target,
                                    "reason": "kicked",
                                })

                        gc = self.group_calls.get(group_id)
                        if gc and target in gc["participants"]:
                            gc["participants"].discard(target)

                elif t == "cloud_put":
                    if not authed_alias:
                        continue
                    owner = msg.get("owner")
                    kind = msg.get("kind")
                    created_at = msg.get("created_at")
                    nonce = msg.get("nonce")
                    ct = msg.get("ct")
                    sig = msg.get("sig")
                    file_id = msg.get("file_id")
                    if owner != authed_alias:
                        continue
                    if not isinstance(kind, str) or kind not in ("db", "file"):
                        continue
                    if not isinstance(created_at, int):
                        created_at = now_ts()
                    if not isinstance(nonce, str) or not isinstance(ct, str) or not isinstance(sig, str):
                        continue
                    if len(nonce) > MAX_NONCE_LEN:
                        continue

                    lim = MAX_CLOUD_DB_BYTES if kind == "db" else MAX_CLOUD_FILE_BYTES

                    if len(ct) > int(lim * 1.45) + 64:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": "", "error": "TOO_LARGE"})
                        continue
                    try:
                        ct_b = b64d(ct)
                    except Exception:
                        continue
                    if len(ct_b) <= 0:
                        continue

                    blob_sha = hashlib.sha256(ct_b).hexdigest()
                    lim = MAX_CLOUD_DB_BYTES if kind == "db" else MAX_CLOUD_FILE_BYTES
                    if len(ct_b) > lim:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "TOO_LARGE"})
                        continue

                    sig_raw = _decode_b64_sig_ed25519(sig)
                    if sig_raw is None:
                        await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "BAD_SIG"})
                        continue

                    if kind == "file":
                        derived = compute_file_id_from_container(ct_b)
                        if not isinstance(derived, str) or not self._HEX64.match(derived):
                            await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "BAD_FILE_CONTAINER"})
                            continue
                        if str(file_id) != str(derived):
                            await self.send(ws, {"type": "cloud_put_err", "blob_sha256": blob_sha, "error": "FILE_ID_MISMATCH"})
                            continue
                    meta = {
                        "owner": owner,
                        "kind": kind,
                        "created_at": int(created_at),
                        "blob_sha256": blob_sha,
                        "bytes_total": int(len(ct_b)),
                    }
                    if kind == "file":
                        if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                            continue
                        meta["file_id"] = str(file_id)
                    try:
                        acc = self.db.get_account(owner)
                        if not acc:
                            continue
                        vk = VerifyKey(b64d(acc.sign_pub_b64))
                        vk.verify(("cloud_put|" + canonical_json(meta)).encode(), sig_raw)
                    except Exception:
                        continue

                    self.db.add_cloud_index(owner, kind, blob_sha, len(ct_b), int(created_at), sig, file_id=str(file_id or ""))

                    node_payload = {
                        "type": "node_store",
                        "owner": owner,
                        "kind": kind,
                        "created_at": int(created_at),
                        "blob_sha256": blob_sha,
                        "file_id": str(file_id or ""),
                        "bytes_total": int(len(ct_b)),
                        "nonce": nonce,
                        "ct": ct,
                        "sig": sig,
                        "owner_sign_pub": str(acc.sign_pub_b64) if acc else "",
                    }
                    for nid, nws in list(self.nodes.items()):
                        if self.db.is_node_banned(nid):
                            continue
                        try:
                            await self.send(nws, node_payload)
                        except Exception:
                            pass

                    await self.send(ws, {"type": "cloud_put_ok", "blob_sha256": blob_sha})

                elif t == "cloud_del":
                    if not authed_alias:
                        continue
                    owner = msg.get("owner")
                    kind = msg.get("kind")
                    blob_sha = msg.get("blob_sha256")
                    sig = msg.get("sig")
                    if owner != authed_alias:
                        continue
                    if not isinstance(kind, str) or kind not in ("db", "file"):
                        continue
                    if not isinstance(blob_sha, str) or not self._HEX64.match(blob_sha):
                        continue
                    if not isinstance(sig, str) or not sig:
                        continue

                    meta = {
                        "owner": owner,
                        "kind": kind,
                        "blob_sha256": blob_sha,
                    }
                    try:
                        acc = self.db.get_account(owner)
                        if not acc:
                            continue
                        vk = VerifyKey(b64d(acc.sign_pub_b64))
                        vk.verify(("cloud_del|" + canonical_json(meta)).encode(), b64d(sig))
                    except Exception:
                        continue

                    self.db.delete_cloud_index(owner, kind, blob_sha)
                    node_payload = {"type": "node_delete", "owner": owner, "kind": kind, "blob_sha256": blob_sha}
                    for nid, nws in list(self.nodes.items()):
                        if self.db.is_node_banned(nid):
                            continue
                        try:
                            await self.send(nws, node_payload)
                        except Exception:
                            pass

                    await self.send(ws, {"type": "cloud_del_ok", "blob_sha256": blob_sha})

                elif t == "cloud_get":
                    if not authed_alias:
                        continue
                    owner = msg.get("owner")
                    kind = msg.get("kind")
                    req_id = msg.get("req_id")
                    if owner != authed_alias:
                        continue
                    if not isinstance(kind, str) or kind not in ("db", "file"):
                        continue
                    if not isinstance(req_id, str) or not req_id or len(req_id) > 96:
                        continue

                    if kind == "db":
                        asyncio.create_task(self._cloud_get_db_verified(ws, owner, req_id))
                        continue

                    await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "USE_BY_SHA"})
                    continue

                elif t == "cloud_get_by_sha":

                    if not authed_alias:
                        continue
                    kind = msg.get("kind")
                    req_id = msg.get("req_id")
                    blob_sha = msg.get("blob_sha256")
                    want_file_id = msg.get("file_id")
                    if not isinstance(kind, str) or kind not in ("file",):
                        continue
                    if not isinstance(req_id, str) or not req_id or len(req_id) > 96:
                        continue
                    if not isinstance(blob_sha, str) or not self._HEX64.match(blob_sha):
                        continue
                    if want_file_id is not None and (not isinstance(want_file_id, str) or not self._HEX64.match(want_file_id)):
                        continue

                    meta = self.db.get_cloud_blob_by_sha(kind, blob_sha)
                    if not meta:
                        await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NOT_FOUND"})
                        continue

                    if isinstance(want_file_id, str) and want_file_id:
                        have_fid = meta.get("file_id") if isinstance(meta, dict) else None
                        if isinstance(have_fid, str) and have_fid and have_fid != want_file_id:
                            await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "FILE_ID_MISMATCH"})
                            continue

                    self._cloud_waiters[req_id] = {
                        "ws": ws,
                        "requester": authed_alias,
                        "blob_sha256": blob_sha,
                        "owner": meta.get("owner_alias"),
                        "kind": kind,
                        "file_id": meta.get("file_id") or (want_file_id if isinstance(want_file_id, str) else ""),
                        "bytes_total": meta.get("bytes_total"),
                        "created_at": meta.get("created_at"),
                        "ts": time.time(),
                        "nodes_sent": 0,
                        "nodes_responded": 0,
                    }

                    try:
                        self._log(
                            "INFO",
                            "cloud_get_by_sha.request",
                            requester=authed_alias,
                            req_id=req_id,
                            kind=kind,
                            blob_sha256=str(blob_sha)[:12],
                            file_id=(str(want_file_id)[:12] if isinstance(want_file_id, str) and want_file_id else None),
                            nodes_total=len(self.nodes),
                            node_ids=(sorted(list(self.nodes.keys())) if len(self.nodes) <= 5 else None),
                        )
                    except Exception:
                        pass

                    sent = 0
                    for nid, nws in list(self.nodes.items()):
                        if self.db.is_node_banned(nid):
                            continue
                        try:
                            if await self.send(nws, {"type": "node_fetch", "req_id": req_id, "blob_sha256": blob_sha}):
                                sent += 1
                        except Exception:
                            pass
                    try:
                        st = self._cloud_waiters.get(req_id)
                        if isinstance(st, dict):
                            st["nodes_sent"] = int(sent)
                            st["nodes_responded"] = int(st.get("nodes_responded") or 0)
                    except Exception:
                        pass
                    if sent == 0:
                        self._cloud_waiters.pop(req_id, None)
                        try:
                            self._log("WARN", "cloud_get_by_sha.no_nodes", requester=authed_alias, req_id=req_id, kind=kind)
                        except Exception:
                            pass
                        await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NO_NODES", "nodes_sent": 0, "nodes_responded": 0})

                elif t == "cloud_get_file":

                    if not authed_alias:
                        continue
                    req_id = msg.get("req_id")
                    file_id = msg.get("file_id")
                    if not isinstance(req_id, str) or not req_id or len(req_id) > 96:
                        continue
                    if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                        continue

                    meta = self.db.get_cloud_file_by_file_id(file_id)
                    blob_sha = meta.get("blob_sha256") if isinstance(meta, dict) else None

                    fetch_mode = "by_sha"
                    if not isinstance(blob_sha, str) or not self._HEX64.match(blob_sha):
                        blob_sha = None
                        fetch_mode = "by_file_id"

                    self._cloud_waiters[req_id] = {
                        "ws": ws,
                        "requester": authed_alias,
                        "blob_sha256": blob_sha,
                        "owner": (meta.get("owner_alias") if isinstance(meta, dict) else None),
                        "kind": "file",
                        "file_id": file_id,
                        "bytes_total": (meta.get("bytes_total") if isinstance(meta, dict) else 0),
                        "created_at": (meta.get("created_at") if isinstance(meta, dict) else 0),
                        "ts": time.time(),
                        "nodes_sent": 0,
                        "nodes_responded": 0,
                        "fetch_mode": fetch_mode,
                    }

                    try:
                        self._log(
                            "INFO",
                            "cloud_get_file.request",
                            requester=authed_alias,
                            req_id=req_id,
                            file_id=str(file_id)[:12],
                            fetch_mode=fetch_mode,
                            blob_sha256=(str(blob_sha)[:12] if isinstance(blob_sha, str) else None),
                            nodes_total=len(self.nodes),
                            node_ids=(sorted(list(self.nodes.keys())) if len(self.nodes) <= 5 else None),
                        )
                    except Exception:
                        pass

                    sent = 0
                    for nid, nws in list(self.nodes.items()):
                        if self.db.is_node_banned(nid):
                            continue
                        try:
                            if fetch_mode == "by_sha":
                                ok = await self.send(nws, {"type": "node_fetch", "req_id": req_id, "blob_sha256": str(blob_sha)})
                            else:
                                ok = await self.send(nws, {"type": "node_fetch_file", "req_id": req_id, "file_id": str(file_id)})
                            if ok:
                                sent += 1
                        except Exception:
                            pass
                    try:
                        st = self._cloud_waiters.get(req_id)
                        if isinstance(st, dict):
                            st["nodes_sent"] = int(sent)
                            st["nodes_responded"] = int(st.get("nodes_responded") or 0)
                    except Exception:
                        pass
                    if sent == 0:
                        self._cloud_waiters.pop(req_id, None)
                        try:
                            self._log(
                                "WARN",
                                "cloud_get_file.no_nodes",
                                requester=authed_alias,
                                req_id=req_id,
                                file_id=str(file_id)[:12],
                                fetch_mode=fetch_mode,
                            )
                        except Exception:
                            pass
                        await self.send(ws, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NO_NODES", "nodes_sent": 0, "nodes_responded": 0})

                elif t == "node_db_candidates":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    owner = msg.get("owner")
                    items = msg.get("items")
                    if not isinstance(req_id, str) or req_id not in self._meta_waiters:
                        continue
                    st = self._meta_waiters.get(req_id)
                    if not isinstance(st, dict) or st.get("mode") != "candidates":
                        continue
                    if not isinstance(owner, str) or owner != st.get("owner"):
                        continue
                    try:
                        st["nodes_responded"] = int(st.get("nodes_responded") or 0) + 1
                    except Exception:
                        pass
                    if isinstance(items, list):
                        out_items = st.get("items")
                        if not isinstance(out_items, list):
                            out_items = []
                            st["items"] = out_items
                        for it in items:
                            if not isinstance(it, dict):
                                continue
                            it2 = dict(it)
                            it2["node_id"] = str(node_id)
                            out_items.append(it2)

                    target = int(st.get("target") or 1)
                    responded = int(st.get("nodes_responded") or 0)
                    if responded >= target:
                        fut = st.get("fut")
                        if fut is not None and hasattr(fut, "done") and not fut.done():
                            try:
                                fut.set_result(st)
                            except Exception:
                                pass

                elif t == "node_db_has_newer":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    owner = msg.get("owner")
                    item = msg.get("item")
                    if not isinstance(req_id, str) or req_id not in self._meta_waiters:
                        continue
                    st = self._meta_waiters.get(req_id)
                    if not isinstance(st, dict) or st.get("mode") != "has_newer":
                        continue
                    if not isinstance(owner, str) or owner != st.get("owner"):
                        continue
                    try:
                        st["nodes_responded"] = int(st.get("nodes_responded") or 0) + 1
                    except Exception:
                        pass
                    if isinstance(item, dict) and item:
                        it2 = dict(item)
                        it2["node_id"] = str(node_id)
                        out_items = st.get("items")
                        if not isinstance(out_items, list):
                            out_items = []
                            st["items"] = out_items
                        out_items.append(it2)

                    target = int(st.get("target") or 1)
                    responded = int(st.get("nodes_responded") or 0)
                    if responded >= target:
                        fut = st.get("fut")
                        if fut is not None and hasattr(fut, "done") and not fut.done():
                            try:
                                fut.set_result(st)
                            except Exception:
                                pass

                elif t == "node_blob_begin":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    okv = msg.get("ok")
                    blob_sha = msg.get("blob_sha256")
                    bytes_total = msg.get("bytes_total")
                    resp_file_id = msg.get("file_id")
                    resp_sig = msg.get("sig") or msg.get("sig_b64")
                    resp_owner = msg.get("owner")
                    resp_kind = msg.get("kind")
                    resp_created_at = msg.get("created_at")
                    if not isinstance(req_id, str) or req_id not in self._cloud_waiters:
                        continue
                    st = self._cloud_waiters.get(req_id)
                    if not st:
                        continue

                    try:
                        mp = st.get("_node_send_ts")
                        if isinstance(mp, dict) and str(node_id) in mp and not st.get("_rtt_done_" + str(node_id)):
                            sent_at = float(mp.get(str(node_id)) or 0.0)
                            if sent_at > 0:
                                self._node_record_fetch_rtt(str(node_id), (time.time() - sent_at) * 1000.0, ok=bool(okv is True))
                                st["_rtt_done_" + str(node_id)] = True
                    except Exception:
                        pass

                    if st.get("node_target") and str(st.get("node_target")) == str(node_id):
                        continue

                    chosen = st.get("chosen_node_id")
                    if chosen and str(chosen) != str(node_id):
                        continue

                    wsw = st.get("ws")
                    if not wsw:
                        self._cloud_waiters.pop(req_id, None)
                        continue

                    try:
                        st["ts"] = time.time()
                    except Exception:
                        pass

                    try:
                        st["nodes_responded"] = int(st.get("nodes_responded") or 0) + 1
                    except Exception:
                        pass

                    try:
                        self._log(
                            "DEBUG",
                            "node_blob_begin.recv",
                            req_id=req_id,
                            node_id=str(node_id),
                            ok=bool(okv is True),
                            kind=st.get("kind"),
                            fetch_mode=st.get("fetch_mode"),
                            file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                            blob_sha256=(str(blob_sha)[:12] if isinstance(blob_sha, str) else None),
                            bytes_total=bytes_total,
                        )
                    except Exception:
                        pass

                    if okv is not True:
                        try:
                            sent = int(st.get("nodes_sent") or 0)
                            responded = int(st.get("nodes_responded") or 0)
                        except Exception:
                            sent, responded = 0, 0
                        if sent > 0 and responded >= sent:

                            try:
                                if st.get("kind") == "db" and await self._cloud_try_next_node(req_id):
                                    continue
                            except Exception:
                                pass

                            try:
                                if (
                                    st.get("kind") == "file"
                                    and st.get("fetch_mode") == "by_sha"
                                    and not st.get("fallback_tried")
                                    and isinstance(st.get("file_id"), str)
                                    and self._HEX64.match(str(st.get("file_id")))
                                ):
                                    st["fallback_tried"] = True
                                    st["fetch_mode"] = "by_file_id"
                                    st["blob_sha256"] = None
                                    st["bytes_total"] = 0
                                    st["chosen_node_id"] = None
                                    st["nodes_sent"] = 0
                                    st["nodes_responded"] = 0
                                    st["ts"] = time.time()
                                    sent2 = 0
                                    for nid, nws in list(self.nodes.items()):
                                        if self.db.is_node_banned(nid):
                                            continue
                                        try:
                                            if await self.send(nws, {"type": "node_fetch_file", "req_id": req_id, "file_id": str(st.get("file_id"))}):
                                                sent2 += 1
                                        except Exception:
                                            pass
                                    st["nodes_sent"] = int(sent2)
                                    try:
                                        self._log(
                                            "INFO",
                                            "cloud_get_file.fallback_to_file_id",
                                            req_id=req_id,
                                            requester=st.get("requester"),
                                            file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                                            sent=sent2,
                                        )
                                    except Exception:
                                        pass
                                    if sent2 == 0:
                                        self._cloud_waiters.pop(req_id, None)
                                        await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NO_NODES", "nodes_sent": 0, "nodes_responded": 0})
                                    continue
                            except Exception:
                                pass

                            self._cloud_waiters.pop(req_id, None)
                            try:
                                self._log(
                                    "INFO",
                                    "cloud_get_file.not_seeded",
                                    req_id=req_id,
                                    requester=st.get("requester"),
                                    kind=st.get("kind"),
                                    fetch_mode=st.get("fetch_mode"),
                                    file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                                    blob_sha256=(str(st.get("blob_sha256"))[:12] if st.get("blob_sha256") else None),
                                    nodes_sent=sent,
                                    nodes_responded=responded,
                                )
                            except Exception:
                                pass
                            await self.send(
                                wsw,
                                {
                                    "type": "cloud_blob",
                                    "req_id": req_id,
                                    "ok": False,
                                    "error": "NOT_SEEDED",
                                    "nodes_sent": sent,
                                    "nodes_responded": responded,
                                },
                            )
                        continue

                    try:
                        k = st.get("kind")
                    except Exception:
                        k = None
                    if k == "file":
                        expected_file_id = st.get("file_id")

                        if isinstance(resp_file_id, str) and resp_file_id and isinstance(resp_sig, str) and resp_sig:
                            if not self._HEX64.match(resp_file_id):
                                self.db.ban_node(str(node_id), "bad_file_id")
                                try:
                                    await ws.close(1008, "bad_meta")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue
                            if isinstance(expected_file_id, str) and expected_file_id and resp_file_id != expected_file_id:
                                self.db.ban_node(str(node_id), "file_id_mismatch")
                                try:
                                    await ws.close(1008, "bad_meta")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue

                            owner_for_sig = str(resp_owner or st.get("owner") or "")
                            try:
                                created_for_sig = int(resp_created_at or st.get("created_at") or 0)
                            except Exception:
                                created_for_sig = 0
                            meta = {
                                "owner": owner_for_sig,
                                "kind": "file",
                                "created_at": int(created_for_sig),
                                "blob_sha256": str(blob_sha or ""),
                                "bytes_total": int(bytes_total or 0),
                                "file_id": str(resp_file_id),
                            }
                            if resp_kind not in (None, "file"):
                                self.db.ban_node(str(node_id), "bad_meta")
                                try:
                                    await ws.close(1008, "bad_meta")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue
                            if not self._verify_cloud_put_meta_sig(owner_for_sig, meta, str(resp_sig)):
                                self.db.ban_node(str(node_id), "bad_sig")
                                try:
                                    await ws.close(1008, "bad_sig")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue

                        if (not isinstance(expected_file_id, str) or not expected_file_id) and isinstance(resp_file_id, str) and resp_file_id:
                            st["file_id"] = str(resp_file_id)

                    st_blob_sha = st.get("blob_sha256")
                    if isinstance(st_blob_sha, str) and st_blob_sha:
                        if not isinstance(blob_sha, str) or blob_sha != st_blob_sha:
                            self.db.ban_node(str(node_id), "bad_blob_hash")
                            try:
                                await ws.close(1008, "bad_blob")
                            except Exception:
                                pass
                            self.nodes.pop(str(node_id), None)
                            self.node_caps.pop(str(node_id), None)
                            self._cloud_waiters.pop(req_id, None)
                            await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "BAD_NODE"})
                            continue
                    else:

                        if isinstance(blob_sha, str) and self._HEX64.match(blob_sha):
                            st["blob_sha256"] = str(blob_sha)

                    try:
                        total_i = int(bytes_total)
                    except Exception:
                        total_i = 0
                    if total_i <= 0:
                        continue

                    k = st.get("kind")
                    if k == "db" and total_i > MAX_CLOUD_DB_BYTES:
                        continue
                    if k == "file" and total_i > MAX_CLOUD_FILE_BYTES:
                        continue

                    try:
                        expected = int(st.get("bytes_total") or 0)
                    except Exception:
                        expected = 0
                    if (not expected) and isinstance(bytes_total, int) and int(bytes_total) > 0:
                        st["bytes_total"] = int(bytes_total)
                        expected = int(bytes_total)
                    if expected and total_i != expected:
                        self.db.ban_node(str(node_id), "bad_blob_size")
                        try:
                            await ws.close(1008, "bad_blob")
                        except Exception:
                            pass
                        self.nodes.pop(str(node_id), None)
                        self.node_caps.pop(str(node_id), None)
                        self._cloud_waiters.pop(req_id, None)
                        await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "BAD_NODE"})
                        continue

                    st["chosen_node_id"] = str(node_id)
                    st["chunk_expected"] = int(total_i)
                    st["chunk_received"] = 0
                    st["chunk_next_idx"] = 0
                    st["chunk_hash"] = hashlib.sha256()

                    if st.get("_republish_db") and st.get("kind") == "db":
                        st["_republish_buf"] = bytearray()

                    await self.send(
                        wsw,
                        {
                            "type": "cloud_blob_begin",
                            "req_id": req_id,
                            "ok": True,
                            "owner": st.get("owner"),
                            "kind": st.get("kind"),
                            "created_at": st.get("created_at"),
                            "blob_sha256": st.get("blob_sha256"),
                            "file_id": st.get("file_id"),
                            "bytes_total": st.get("bytes_total") or int(total_i),
                            "nodes_sent": st.get("nodes_sent"),
                            "nodes_responded": st.get("nodes_responded"),
                        },
                    )

                elif t == "node_blob_chunk":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    idx = msg.get("idx")
                    data = msg.get("data")
                    if not isinstance(req_id, str) or req_id not in self._cloud_waiters:
                        continue
                    st = self._cloud_waiters.get(req_id)
                    if not st:
                        continue
                    if str(st.get("chosen_node_id") or "") != str(node_id):
                        continue
                    wsw = st.get("ws")
                    if not wsw:
                        self._cloud_waiters.pop(req_id, None)
                        continue

                    try:
                        st["ts"] = time.time()
                    except Exception:
                        pass
                    if not isinstance(idx, int) or not isinstance(data, str):
                        continue
                    if int(idx) != int(st.get("chunk_next_idx") or 0):
                        continue
                    try:
                        chunk_b = b64d(data)
                    except Exception:
                        continue

                    if st.get("_republish_db") and st.get("kind") == "db":
                        try:
                            buf = st.get("_republish_buf")
                            if isinstance(buf, bytearray):
                                if (len(buf) + len(chunk_b)) <= MAX_CLOUD_DB_BYTES:
                                    buf.extend(chunk_b)
                        except Exception:
                            pass

                    try:
                        expected = int(st.get("chunk_expected") or 0)
                    except Exception:
                        expected = 0
                    try:
                        received = int(st.get("chunk_received") or 0)
                    except Exception:
                        received = 0
                    if expected and (received + len(chunk_b)) > expected:
                        continue

                    hh = st.get("chunk_hash")
                    if hasattr(hh, "update"):
                        try:
                            hh.update(chunk_b)
                        except Exception:
                            pass
                    st["chunk_received"] = received + len(chunk_b)
                    st["chunk_next_idx"] = int(st.get("chunk_next_idx") or 0) + 1

                    await self.send(wsw, {"type": "cloud_blob_chunk", "req_id": req_id, "idx": int(idx), "data": data})

                elif t == "node_blob_end":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    if not isinstance(req_id, str) or req_id not in self._cloud_waiters:
                        continue
                    st = self._cloud_waiters.get(req_id)
                    if not st:
                        continue
                    if str(st.get("chosen_node_id") or "") != str(node_id):
                        continue
                    wsw = st.get("ws")
                    if not wsw:
                        self._cloud_waiters.pop(req_id, None)
                        continue

                    try:
                        st["ts"] = time.time()
                    except Exception:
                        pass

                    try:
                        expected = int(st.get("chunk_expected") or 0)
                    except Exception:
                        expected = 0
                    try:
                        received = int(st.get("chunk_received") or 0)
                    except Exception:
                        received = 0
                    hh = st.get("chunk_hash")
                    digest = None
                    try:
                        digest = hh.hexdigest() if hasattr(hh, "hexdigest") else None
                    except Exception:
                        digest = None

                    if (expected and received != expected) or (isinstance(digest, str) and digest != st.get("blob_sha256")):
                        self.db.ban_node(str(node_id), "bad_blob")
                        try:
                            await ws.close(1008, "bad_blob")
                        except Exception:
                            pass
                        self.nodes.pop(str(node_id), None)
                        self.node_caps.pop(str(node_id), None)

                        await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "BAD_NODE"})
                        await self.send(wsw, {"type": "cloud_blob_end", "req_id": req_id})
                        self._cloud_waiters.pop(req_id, None)
                        continue

                    try:
                        if st.get("_republish_db") and st.get("kind") == "db":
                            owner2 = str(st.get("owner") or "")
                            sha2 = str(st.get("blob_sha256") or "")
                            sig2 = str(st.get("sig_b64") or "")
                            created2 = int(st.get("created_at") or 0)
                            bytes2 = int(st.get("bytes_total") or expected or 0)
                            buf2 = st.get("_republish_buf")
                            if isinstance(buf2, bytearray) and owner2 and sha2 and sig2 and created2 > 0 and bytes2 > 0:
                                asyncio.create_task(
                                    self._republish_db_snapshot_to_nodes(
                                        owner=owner2,
                                        blob_sha256=sha2,
                                        created_at=created2,
                                        bytes_total=bytes2,
                                        sig_b64=sig2,
                                        blob=bytes(buf2),
                                        skip_node_id=str(st.get("chosen_node_id") or "") or None,
                                        limit=3,
                                    )
                                )
                    except Exception:
                        pass

                    await self.send(wsw, {"type": "cloud_blob_end", "req_id": req_id})
                    self._cloud_waiters.pop(req_id, None)

                elif t == "node_blob":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    okv = msg.get("ok")
                    ct = msg.get("ct")
                    resp_file_id = msg.get("file_id")
                    resp_sig = msg.get("sig") or msg.get("sig_b64")
                    resp_owner = msg.get("owner")
                    resp_kind = msg.get("kind")
                    resp_created_at = msg.get("created_at")
                    if not isinstance(req_id, str) or req_id not in self._cloud_waiters:
                        continue
                    st = self._cloud_waiters.get(req_id)
                    if not st:
                        continue

                    try:
                        mp = st.get("_node_send_ts")
                        if isinstance(mp, dict) and str(node_id) in mp and not st.get("_rtt_done_" + str(node_id)):
                            sent_at = float(mp.get(str(node_id)) or 0.0)
                            if sent_at > 0:
                                self._node_record_fetch_rtt(str(node_id), (time.time() - sent_at) * 1000.0, ok=bool(okv is True))
                                st["_rtt_done_" + str(node_id)] = True
                    except Exception:
                        pass

                    if st.get("node_target") and str(st.get("node_target")) == str(node_id):
                        continue

                    chosen = st.get("chosen_node_id")
                    if chosen and str(chosen) != str(node_id):
                        continue

                    wsw = st.get("ws")
                    if not wsw:
                        self._cloud_waiters.pop(req_id, None)
                        continue

                    try:
                        st["ts"] = time.time()
                    except Exception:
                        pass

                    try:
                        st["nodes_responded"] = int(st.get("nodes_responded") or 0) + 1
                    except Exception:
                        pass

                    if okv is not True:

                        try:
                            sent = int(st.get("nodes_sent") or 0)
                            responded = int(st.get("nodes_responded") or 0)
                        except Exception:
                            sent, responded = 0, 0
                        if sent > 0 and responded >= sent:

                            try:
                                if st.get("kind") == "db" and await self._cloud_try_next_node(req_id):
                                    continue
                            except Exception:
                                pass

                            try:
                                if (
                                    st.get("kind") == "file"
                                    and st.get("fetch_mode") == "by_sha"
                                    and not st.get("fallback_tried")
                                    and isinstance(st.get("file_id"), str)
                                    and self._HEX64.match(str(st.get("file_id")))
                                ):
                                    st["fallback_tried"] = True
                                    st["fetch_mode"] = "by_file_id"
                                    st["blob_sha256"] = None
                                    st["bytes_total"] = 0
                                    st["chosen_node_id"] = None
                                    st["nodes_sent"] = 0
                                    st["nodes_responded"] = 0
                                    st["ts"] = time.time()
                                    sent2 = 0
                                    for nid, nws in list(self.nodes.items()):
                                        if self.db.is_node_banned(nid):
                                            continue
                                        try:
                                            if await self.send(nws, {"type": "node_fetch_file", "req_id": req_id, "file_id": str(st.get("file_id"))}):
                                                sent2 += 1
                                        except Exception:
                                            pass
                                    st["nodes_sent"] = int(sent2)
                                    try:
                                        self._log(
                                            "INFO",
                                            "cloud_get_file.fallback_to_file_id",
                                            req_id=req_id,
                                            requester=st.get("requester"),
                                            file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                                            sent=sent2,
                                        )
                                    except Exception:
                                        pass
                                    if sent2 == 0:
                                        self._cloud_waiters.pop(req_id, None)
                                        await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "NO_NODES", "nodes_sent": 0, "nodes_responded": 0})
                                    continue
                            except Exception:
                                pass

                            self._cloud_waiters.pop(req_id, None)
                            try:
                                self._log(
                                    "INFO",
                                    "cloud_get_file.not_seeded",
                                    req_id=req_id,
                                    requester=st.get("requester"),
                                    kind=st.get("kind"),
                                    fetch_mode=st.get("fetch_mode"),
                                    file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                                    blob_sha256=(str(st.get("blob_sha256"))[:12] if st.get("blob_sha256") else None),
                                    nodes_sent=sent,
                                    nodes_responded=responded,
                                )
                            except Exception:
                                pass
                            await self.send(
                                wsw,
                                {
                                    "type": "cloud_blob",
                                    "req_id": req_id,
                                    "ok": False,
                                    "error": "NOT_SEEDED",
                                    "nodes_sent": sent,
                                    "nodes_responded": responded,
                                },
                            )
                        continue
                    if not isinstance(ct, str):
                        continue

                    try:
                        expected = int(st.get("bytes_total") or 0)
                    except Exception:
                        expected = 0
                    if expected > 0 and len(ct) > int(expected * 1.45) + 64:
                        continue
                    try:
                        ct_b = b64d(ct)
                    except Exception:
                        continue

                    k = st.get("kind")
                    if k == "db" and len(ct_b) > MAX_CLOUD_DB_BYTES:
                        continue
                    if k == "file" and len(ct_b) > MAX_CLOUD_FILE_BYTES:
                        continue

                    if not expected:
                        try:
                            st["bytes_total"] = int(len(ct_b))
                        except Exception:
                            pass
                        expected = int(len(ct_b))

                    if expected and len(ct_b) != expected:
                        self.db.ban_node(str(node_id), "bad_blob_size")
                        try:
                            await ws.close(1008, "bad_blob")
                        except Exception:
                            pass
                        self.nodes.pop(str(node_id), None)
                        self.node_caps.pop(str(node_id), None)
                        self._cloud_waiters.pop(req_id, None)
                        await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "BAD_NODE"})
                        continue

                    sha = hashlib.sha256(ct_b).hexdigest()

                    try:
                        self._log(
                            "DEBUG",
                            "node_blob.recv",
                            req_id=req_id,
                            node_id=str(node_id),
                            kind=st.get("kind"),
                            fetch_mode=st.get("fetch_mode"),
                            ok=True,
                            sha=str(sha)[:12],
                            bytes_total=len(ct_b),
                            file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                            requester=st.get("requester"),
                        )
                    except Exception:
                        pass

                    try:
                        k = st.get("kind")
                    except Exception:
                        k = None
                    if k == "file":
                        expected_file_id = st.get("file_id")
                        if isinstance(resp_file_id, str) and resp_file_id and isinstance(resp_sig, str) and resp_sig:
                            if not self._HEX64.match(resp_file_id):
                                self.db.ban_node(str(node_id), "bad_file_id")
                                try:
                                    await ws.close(1008, "bad_meta")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue
                            if isinstance(expected_file_id, str) and expected_file_id and resp_file_id != expected_file_id:
                                self.db.ban_node(str(node_id), "file_id_mismatch")
                                try:
                                    await ws.close(1008, "bad_meta")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue

                            owner_for_sig = str(resp_owner or st.get("owner") or "")
                            try:
                                created_for_sig = int(resp_created_at or st.get("created_at") or 0)
                            except Exception:
                                created_for_sig = 0
                            meta = {
                                "owner": owner_for_sig,
                                "kind": "file",
                                "created_at": int(created_for_sig),
                                "blob_sha256": str(sha),
                                "bytes_total": int(len(ct_b)),
                                "file_id": str(resp_file_id),
                            }
                            if resp_kind not in (None, "file"):
                                self.db.ban_node(str(node_id), "bad_meta")
                                try:
                                    await ws.close(1008, "bad_meta")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue
                            if not self._verify_cloud_put_meta_sig(owner_for_sig, meta, str(resp_sig)):
                                self.db.ban_node(str(node_id), "bad_sig")
                                try:
                                    await ws.close(1008, "bad_sig")
                                except Exception:
                                    pass
                                self.nodes.pop(str(node_id), None)
                                self.node_caps.pop(str(node_id), None)
                                await self._fail_cloud_waiter_if_exhausted(req_id, error="BAD_NODE")
                                continue
                        if (not isinstance(expected_file_id, str) or not expected_file_id) and isinstance(resp_file_id, str) and resp_file_id:
                            st["file_id"] = str(resp_file_id)

                    if not isinstance(st.get("blob_sha256"), str) or not st.get("blob_sha256"):
                        st["blob_sha256"] = sha

                    if (not st.get("owner")) and isinstance(resp_owner, str) and resp_owner:
                        st["owner"] = str(resp_owner)
                    try:
                        if (not int(st.get("created_at") or 0)) and resp_created_at is not None:
                            st["created_at"] = int(resp_created_at)
                    except Exception:
                        pass

                    st["chosen_node_id"] = str(node_id)
                    st_blob_sha = st.get("blob_sha256")
                    if isinstance(st_blob_sha, str) and st_blob_sha:
                        if sha != st_blob_sha:
                            self.db.ban_node(str(node_id), "bad_blob_hash")
                            try:
                                await ws.close(1008, "bad_blob")
                            except Exception:
                                pass
                            self.nodes.pop(str(node_id), None)
                            self.node_caps.pop(str(node_id), None)
                            self._cloud_waiters.pop(req_id, None)
                            await self.send(wsw, {"type": "cloud_blob", "req_id": req_id, "ok": False, "error": "BAD_NODE"})
                            continue
                    else:

                        st["blob_sha256"] = sha

                    await self.send(
                        wsw,
                        {
                            "type": "cloud_blob",
                            "req_id": req_id,
                            "ok": True,
                            "owner": st.get("owner"),
                            "kind": st.get("kind"),
                            "created_at": st.get("created_at"),
                            "blob_sha256": st.get("blob_sha256"),
                            "file_id": st.get("file_id"),
                            "bytes_total": st.get("bytes_total"),
                            "ct": ct,
                            "nodes_sent": st.get("nodes_sent"),
                            "nodes_responded": st.get("nodes_responded"),
                        },
                    )

                    try:
                        if st.get("_republish_db") and st.get("kind") == "db":
                            owner2 = str(st.get("owner") or "")
                            sha2 = str(st.get("blob_sha256") or "")
                            sig2 = str(st.get("sig_b64") or "")
                            created2 = int(st.get("created_at") or 0)
                            bytes2 = int(st.get("bytes_total") or len(ct_b) or 0)
                            if owner2 and sha2 and sig2 and created2 > 0 and bytes2 > 0:
                                asyncio.create_task(
                                    self._republish_db_snapshot_to_nodes(
                                        owner=owner2,
                                        blob_sha256=sha2,
                                        created_at=created2,
                                        bytes_total=bytes2,
                                        sig_b64=sig2,
                                        blob=bytes(ct_b),
                                        skip_node_id=str(st.get("chosen_node_id") or "") or None,
                                        limit=3,
                                    )
                                )
                    except Exception:
                        pass

                    try:
                        self._log(
                            "INFO",
                            "cloud_get_file.ok",
                            req_id=req_id,
                            requester=st.get("requester"),
                            kind=st.get("kind"),
                            fetch_mode=st.get("fetch_mode"),
                            node_id=str(node_id),
                            blob_sha256=(str(st.get("blob_sha256"))[:12] if st.get("blob_sha256") else None),
                            file_id=(str(st.get("file_id"))[:12] if st.get("file_id") else None),
                            bytes_total=st.get("bytes_total"),
                        )
                    except Exception:
                        pass
                    self._cloud_waiters.pop(req_id, None)

                elif t == "node_list":
                    if not node_id:
                        continue
                    since = msg.get("since")
                    if not isinstance(since, int):
                        since = now_ts() - 3600
                    items = self.db.list_cloud_blobs_since(int(since), limit=800)
                    await self.send(ws, {"type": "node_list", "items": items})

                elif t == "node_fetch_cloud":
                    if not node_id:
                        continue
                    req_id = msg.get("req_id")
                    blob_sha = msg.get("blob_sha256")
                    if not isinstance(req_id, str) or not req_id or len(req_id) > 96:
                        continue
                    if not isinstance(blob_sha, str) or not self._HEX64.match(blob_sha):
                        continue

                    self._cloud_waiters[req_id] = {
                        "ws": ws,
                        "blob_sha256": blob_sha,
                        "owner": None,
                        "kind": None,
                        "bytes_total": 0,
                        "created_at": now_ts(),
                        "ts": time.time(),
                        "node_target": node_id,
                    }
                    for nid, nws in list(self.nodes.items()):
                        if nid == node_id:
                            continue
                        if self.db.is_node_banned(nid):
                            continue
                        try:
                            await self.send(nws, {"type": "node_fetch", "req_id": req_id, "blob_sha256": blob_sha})
                        except Exception:
                            pass

                elif t == "file_announce":

                    if not authed_alias:
                        continue
                    file_id = msg.get("file_id")
                    if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                        continue
                    self.file_seeds.setdefault(file_id, set()).add(authed_alias)
                    self.seed_files.setdefault(authed_alias, set()).add(file_id)

                elif t == "file_unannounce":
                    if not authed_alias:
                        continue
                    file_id = msg.get("file_id")
                    if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                        continue
                    seeds = self.file_seeds.get(file_id)
                    if seeds:
                        seeds.discard(authed_alias)
                        if not seeds:
                            self.file_seeds.pop(file_id, None)
                    sfiles = self.seed_files.get(authed_alias)
                    if sfiles:
                        sfiles.discard(file_id)

                elif t == "file_find":
                    if not authed_alias:
                        continue
                    file_id = msg.get("file_id")
                    if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                        continue
                    all_seeds = self.file_seeds.get(file_id, set())
                    total = 0
                    try:
                        total = len(all_seeds)
                    except Exception:
                        total = 0

                    seeds = []
                    for a in all_seeds:
                        ws_a = self.online.get(a)
                        if not ws_a:
                            continue
                        if getattr(ws_a, "closed", False):
                            if self.online.get(a) is ws_a:
                                self.online.pop(a, None)
                            continue
                        seeds.append(a)
                    seeds = sorted(seeds)
                    await self.send(ws, {"type": "file_seeds", "file_id": file_id, "seeds": seeds, "seeds_total": int(total)})

                elif t == "file_get":

                    if not authed_alias:
                        continue
                    file_id = msg.get("file_id")
                    seed = msg.get("seed")
                    transfer_id = msg.get("transfer_id")
                    idx = msg.get("idx")
                    if not isinstance(file_id, str) or not self._HEX64.match(file_id):
                        continue
                    if not isinstance(seed, str) or not isinstance(transfer_id, str) or len(transfer_id) > 96:
                        continue
                    if not isinstance(idx, int) or idx < 0:
                        continue
                    seed_ws = self.online.get(seed)
                    if not seed_ws or getattr(seed_ws, "closed", False):
                        if seed_ws and self.online.get(seed) is seed_ws:
                            self.online.pop(seed, None)
                        await self.send(ws, {"type": "file_err", "transfer_id": transfer_id, "error": "SEED_OFFLINE"})
                        continue

                    if seed not in self.file_seeds.get(file_id, set()):
                        await self.send(ws, {"type": "file_err", "transfer_id": transfer_id, "error": "SEED_NOT_ANNOUNCED"})
                        continue
                    ok = await self.send(
                        seed_ws,
                        {
                            "type": "file_get",
                            "transfer_id": transfer_id,
                            "file_id": file_id,
                            "idx": idx,
                            "requester": authed_alias,
                        },
                    )
                    if not ok:
                        if self.online.get(seed) is seed_ws:
                            self.online.pop(seed, None)
                        await self.send(ws, {"type": "file_err", "transfer_id": transfer_id, "error": "SEED_OFFLINE"})

                elif t == "file_chunk":

                    if not authed_alias:
                        continue
                    to_alias = msg.get("to")
                    transfer_id = msg.get("transfer_id")
                    file_id = msg.get("file_id")
                    idx = msg.get("idx")
                    data_b64 = msg.get("data")
                    if not isinstance(to_alias, str) or not isinstance(transfer_id, str) or not isinstance(file_id, str):
                        continue
                    if not self._HEX64.match(file_id) or len(transfer_id) > 96:
                        continue
                    if not isinstance(idx, int) or idx < 0:
                        continue
                    if not isinstance(data_b64, str) or len(data_b64) > self.MAX_FILE_RECORD_B64:
                        continue
                    to_ws = self.online.get(to_alias)
                    if not to_ws or getattr(to_ws, "closed", False):
                        if to_ws and self.online.get(to_alias) is to_ws:
                            self.online.pop(to_alias, None)
                        continue
                    await self.send(
                        to_ws,
                        {
                            "type": "file_chunk",
                            "transfer_id": transfer_id,
                            "file_id": file_id,
                            "idx": idx,
                            "data": data_b64,
                            "seed": authed_alias,
                        },
                    )

                elif t == "file_err":
                    if not authed_alias:
                        continue
                    to_alias = msg.get("to")
                    transfer_id = msg.get("transfer_id")
                    error = msg.get("error")
                    if not isinstance(to_alias, str) or not isinstance(transfer_id, str):
                        continue
                    to_ws = self.online.get(to_alias)
                    if not to_ws or getattr(to_ws, "closed", False):
                        if to_ws and self.online.get(to_alias) is to_ws:
                            self.online.pop(to_alias, None)
                        continue
                    await self.send(to_ws, {"type": "file_err", "transfer_id": transfer_id, "error": error, "seed": authed_alias})
        except websockets.ConnectionClosed: pass
        finally:
            try:
                self.ip_counts[ip] = self.ip_counts.get(ip, 1) - 1
                if self.ip_counts.get(ip, 0) <= 0:
                    self.ip_counts.pop(ip, None)
            except Exception:
                pass

            self.challenges.pop(ws, None)
            if authed_alias and self.online.get(authed_alias) is ws:
                self.online.pop(authed_alias, None)
                self.last_seen.pop(authed_alias, None)

                group_id = self._alias_in_group_call.pop(authed_alias, None)
                if group_id:
                    gc = self.group_calls.get(group_id)
                    if gc:
                        gc["participants"].discard(authed_alias)

                        for p in gc["participants"]:
                            pws = self.online.get(p)
                            if pws:
                                try:
                                    asyncio.create_task(self.send(pws, {
                                        "type": "group_call_state",
                                        "group_id": group_id,
                                        "call_id": gc["call_id"],
                                        "participants": list(gc["participants"]),
                                        "event": "leave",
                                        "who": authed_alias,
                                    }))
                                except Exception:
                                    pass

                        if not gc["participants"]:
                            self.group_calls.pop(group_id, None)

                for fid in list(self.seed_files.get(authed_alias, set())):
                    seeds = self.file_seeds.get(fid)
                    if seeds:
                        seeds.discard(authed_alias)
                        if not seeds:
                            self.file_seeds.pop(fid, None)
                self.seed_files.pop(authed_alias, None)

            if node_id and self.nodes.get(node_id) is ws:
                self.nodes.pop(node_id, None)
                self.node_caps.pop(node_id, None)
                try:
                    self._ws_node_id.pop(ws, None)
                except Exception:
                    pass
                try:
                    stn = self._node_stats.get(str(node_id))
                    if isinstance(stn, dict):
                        stn["connected"] = False
                except Exception:
                    pass
                try:
                    self._log("INFO", "node.disconnected", node_id=str(node_id), ip=str(masked_ip), nodes_total=len(self.nodes))
                except Exception:
                    pass

async def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--host", default="127.0.0.1")
    ap.add_argument("--port", type=int, default=8765)
    ap.add_argument("--db", default="server.db")
    ap.add_argument("--cert", help="Path to SSL certificate file")
    ap.add_argument("--key", help="Path to SSL key file")
    args = ap.parse_args()
    db = DB(args.db)
    srv = RelayServer(db)

    asyncio.create_task(srv.housekeeping_loop())

    ssl_context = None
    if args.cert and args.key:
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        ssl_context.load_cert_chain(args.cert, args.key)

    async with websockets.serve(
        srv.handler,
        args.host,
        args.port,
        ssl=ssl_context,
        max_size=32 * 1024 * 1024,
        ping_interval=20,
        ping_timeout=120,
        process_request=make_process_request(srv),
    ):
        scheme = "wss" if ssl_context else "ws"
        print(f"Server on {scheme}://{args.host}:{args.port}")
        await asyncio.Future()

def run_server():
    import logging
    logging.getLogger("websockets.server").setLevel(logging.ERROR)
    logging.getLogger("asyncio").setLevel(logging.ERROR)
    asyncio.run(main())

if __name__ == "__main__":
    run_server()
