mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 19:43:53 +08:00
feat: add optional native mqtt and udp transport
This commit is contained in:
@@ -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("密码签名验证失败")
|
||||
Reference in New Issue
Block a user