"""帧编解码与注册地址推导。""" from __future__ import annotations import json from typing import Any from urllib.parse import urlparse, urlunparse def dumps(obj: dict[str, Any]) -> bytes: return json.dumps(obj, ensure_ascii=False, separators=(",", ":")).encode("utf-8") def loads(data: bytes | str) -> dict[str, Any]: if isinstance(data, bytes): data = data.decode("utf-8") return json.loads(data) def register_url_from_connect(connect_url: str) -> str: """从连接地址推出注册 HTTP 地址(DEVELOPMENT 6.9)。""" raw = connect_url.strip() if "://" not in raw: raw = "ws://" + raw u = urlparse(raw) scheme = u.scheme.lower() if scheme in ("wss", "mqtts", "https"): http_scheme = "https" elif scheme in ("ws", "mqtt", "http"): http_scheme = "http" else: http_scheme = "https" if scheme.endswith("s") else "http" # 去掉 /mqtt 路径 path = u.path or "" if path.endswith("/mqtt"): path = path[: -len("/mqtt")] path = path.rstrip("/") + "/api/client/register" return urlunparse((http_scheme, u.netloc, path, "", "", "")) def up_topic(endpoint_id: str) -> str: return f"nix/c/{endpoint_id}/up" def down_topic(endpoint_id: str) -> str: return f"nix/c/{endpoint_id}/down" def normalize_mqtt_ws_url(url: str, *, allow_tcp: bool = False) -> str: """http→ws、https→wss;路径为空或 / 时用 /mqtt,否则保留;mqtt/mqtts 仅显式允许裸 TCP。""" raw = url.strip() if "://" not in raw: raw = "ws://" + raw u = urlparse(raw) scheme = u.scheme.lower() if scheme == "http": scheme = "ws" elif scheme == "https": scheme = "wss" if scheme in ("mqtt", "mqtts"): if not allow_tcp: raise ValueError("裸 TCP 需显式 use_tcp") return urlunparse((scheme, u.netloc, u.path, u.params, u.query, u.fragment)) if scheme not in ("ws", "wss"): raise ValueError(f"unsupported scheme {u.scheme}") path = u.path or "" if path in ("", "/"): path = "/mqtt" return urlunparse((scheme, u.netloc, path, u.params, u.query, u.fragment))