feat: add optional native mqtt and udp transport

This commit is contained in:
caixypromise
2026-07-27 02:10:55 +08:00
parent 0c582ed3b6
commit eac573706d
35 changed files with 6325 additions and 200 deletions
+100
View File
@@ -0,0 +1,100 @@
import base64
import hashlib
import hmac
import json
import re
from config.logger import setup_logging
logger = setup_logging()
TAG = __name__
_MAC_ADDRESS_PATTERN = re.compile(r"^(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$")
_ENDPOINT_SCHEME_PATTERN = re.compile(
r"^(?:mqtt|tcp|ssl|ws|wss|http|https)://", re.IGNORECASE
)
def normalize_signature_key(secret_key: str) -> str:
"""Treat values emitted by manager-api for an unset parameter as empty."""
if secret_key is None:
return ""
value = str(secret_key).strip()
if not value or value.lower() == "null" or "" in value:
return ""
return value
def generate_password_signature(content: str, secret_key: str) -> str:
"""生成MQTT密码签名(HMAC-SHA256 + Base64"""
try:
hmac_obj = hmac.new(
secret_key.encode("utf-8"), content.encode("utf-8"), hashlib.sha256
)
signature = hmac_obj.digest()
return base64.b64encode(signature).decode("utf-8")
except Exception as e:
logger.bind(tag=TAG).error(f"生成MQTT密码签名失败: {e}")
return ""
def parse_mqtt_endpoint(endpoint: str, default_port: int = None) -> tuple[str, int]:
"""Parse the host[:port] syntax supported by the current ESP firmware."""
if endpoint is None:
return "", default_port
value = str(endpoint).strip()
if not value or value.lower() == "null" or "" in value:
return "", default_port
value = _ENDPOINT_SCHEME_PATTERN.sub("", value, count=1).split("/", 1)[0]
if not value or value.startswith("[") or value.count(":") > 1:
raise ValueError("MQTT endpoint格式无效")
host = value
port = default_port
if ":" in value:
host, port_text = value.rsplit(":", 1)
if not port_text.isdigit():
raise ValueError("MQTT endpoint端口无效")
port = int(port_text)
if not host or any(char.isspace() for char in host):
raise ValueError("MQTT endpoint主机无效")
if port is not None and not 1 <= int(port) <= 65535:
raise ValueError("MQTT endpoint端口超出范围")
return host, int(port) if port is not None else None
def validate_mqtt_credentials(
client_id: str, username: str, password: str, secret_key: str
) -> None:
"""Validate the gateway-compatible MQTT client id and HMAC credentials."""
if not client_id or not isinstance(client_id, str):
raise ValueError("clientId必须是非空字符串")
parts = client_id.split("@@@")
if len(parts) not in (2, 3) or not parts[0] or not parts[1]:
raise ValueError("clientId格式错误")
mac_address = parts[1].replace("_", ":")
if not _MAC_ADDRESS_PATTERN.fullmatch(mac_address):
raise ValueError("clientId中的MAC地址无效")
normalized_key = normalize_signature_key(secret_key)
if len(parts) == 2:
if normalized_key:
raise ValueError("启用签名时clientId必须包含UUID")
return
if not username or not isinstance(username, str):
raise ValueError("username必须是非空字符串")
try:
user_data = json.loads(base64.b64decode(username, validate=True).decode("utf-8"))
if not isinstance(user_data, dict):
raise ValueError
except Exception as exc:
raise ValueError("username不是有效的base64编码JSON") from exc
if normalized_key:
expected = generate_password_signature(client_id + "|" + username, normalized_key)
if not password or not hmac.compare_digest(password, expected):
raise ValueError("密码签名验证失败")