325 lines
10 KiB
Python
325 lines
10 KiB
Python
"""
|
|
TAF/WUP 帧解码器 — 将二进制帧转为可读摘要,用于日志输出
|
|
"""
|
|
|
|
from typing import Any, cast
|
|
from .taf_protocol import TafInputStream, TafType
|
|
from .wup_protocol import normalize_wup_payload
|
|
|
|
# cmd 编号 → 名称
|
|
CMD_NAMES = {
|
|
0x03: "RPC_REQ",
|
|
0x04: "RPC_RSP",
|
|
0x0A: "AUTH",
|
|
0x0B: "PUSH1",
|
|
0x10: "HB_SEND",
|
|
0x11: "HB_RECV",
|
|
0x17: "CONFIRM",
|
|
0x18: "PUSH2",
|
|
0x21: "REGISTER",
|
|
0x22: "CONFIRM_RSP",
|
|
}
|
|
|
|
# TAIL_BYTES = 2c36004c5c6600
|
|
_TAIL = bytes.fromhex("2c36004c5c6600")
|
|
|
|
|
|
def _strip_tail(body: bytes) -> bytes:
|
|
"""裁掉 body 末尾的 TAIL_BYTES"""
|
|
if body.endswith(_TAIL):
|
|
return body[: -len(_TAIL)]
|
|
# 有时 TAIL 前还有 0c (ZERO tag)
|
|
if (
|
|
len(body) > 1
|
|
and body[-len(_TAIL) - 1 : -len(_TAIL)] == b"\x0c"
|
|
and body.endswith(_TAIL)
|
|
):
|
|
return body[: -len(_TAIL) - 1]
|
|
return body
|
|
|
|
|
|
def _decode_taf_value(ins: TafInputStream, dtype: int, depth: int = 0) -> object:
|
|
"""递归解码单个 TAF 值"""
|
|
if dtype == TafType.ZERO:
|
|
return 0
|
|
if dtype in (TafType.INT8, TafType.INT16, TafType.INT32, TafType.INT64):
|
|
return ins._read_int_value(dtype)
|
|
if dtype in (TafType.FLOAT, TafType.DOUBLE):
|
|
import struct as _s
|
|
|
|
if dtype == TafType.FLOAT:
|
|
return round(_s.unpack(">f", ins.buf.read(4))[0], 4)
|
|
return round(_s.unpack(">d", ins.buf.read(8))[0], 6)
|
|
if dtype == TafType.STRING1:
|
|
ln = ins.buf.read(1)[0]
|
|
return ins.buf.read(ln).decode("utf-8", errors="replace")
|
|
if dtype == TafType.STRING4:
|
|
import struct as _s
|
|
|
|
ln = _s.unpack(">I", ins.buf.read(4))[0]
|
|
return ins.buf.read(ln).decode("utf-8", errors="replace")
|
|
if dtype == TafType.MAP:
|
|
cnt = ins._read_int_len()
|
|
m = {}
|
|
for _ in range(cnt):
|
|
_, kt = ins.read_head()
|
|
k = _decode_taf_value(ins, kt, depth + 1)
|
|
_, vt = ins.read_head()
|
|
v = _decode_taf_value(ins, vt, depth + 1)
|
|
m[k] = v
|
|
return m
|
|
if dtype == TafType.LIST:
|
|
cnt = ins._read_int_len()
|
|
items = []
|
|
for _ in range(cnt):
|
|
_, it = ins.read_head()
|
|
items.append(_decode_taf_value(ins, it, depth + 1))
|
|
return items
|
|
if dtype == TafType.SIMPLE_LIST:
|
|
ins.read_head() # element type INT8
|
|
ln = ins._read_int_len()
|
|
return f"<bytes {ln}B>"
|
|
if dtype == TafType.STRUCT_BEGIN:
|
|
return _decode_taf_struct(ins, depth + 1)
|
|
return f"<0x{dtype:02x}>"
|
|
|
|
|
|
def _decode_taf_struct(ins: TafInputStream, depth: int = 0) -> dict:
|
|
"""解码一个 TAF 结构体 (已消费 STRUCT_BEGIN) → 有序字典"""
|
|
fields = {}
|
|
while True:
|
|
try:
|
|
tag, dtype = ins.peek_head()
|
|
except EOFError:
|
|
break
|
|
if dtype == TafType.STRUCT_END:
|
|
ins.read_head()
|
|
break
|
|
ins.read_head()
|
|
# 限制递归深度
|
|
if depth < 5:
|
|
try:
|
|
val = _decode_taf_value(ins, dtype, depth)
|
|
except Exception:
|
|
val = f"<decode_err:0x{dtype:02x}>"
|
|
else:
|
|
val = f"<...>"
|
|
try:
|
|
ins.skip_field(dtype)
|
|
except Exception:
|
|
break
|
|
key = f"tag{tag}"
|
|
if key in fields:
|
|
key = f"{key}_2"
|
|
fields[key] = val
|
|
return fields
|
|
|
|
|
|
def _extract_wup(body: bytes) -> bytes:
|
|
"""从 WSS 响应 body 中提取干净的 WUP 包"""
|
|
if len(body) < 5:
|
|
return body
|
|
# 大包格式: [1B prefix][4B wup_len][wup_body][tail]
|
|
# prefix 可能是 0x00,必须优先于 4B total_len 判断。
|
|
wup_len = int.from_bytes(body[1:5], "big")
|
|
if 5 + wup_len <= len(body) and body[5:7] == b"\x10\x03":
|
|
return body[5 : 5 + wup_len]
|
|
total_len = int.from_bytes(body[0:4], "big")
|
|
if 8 <= total_len <= len(body) and body[4:6] == b"\x10\x03":
|
|
return body[:total_len]
|
|
if 5 + wup_len <= len(body):
|
|
return body[5 : 5 + wup_len]
|
|
return body
|
|
|
|
|
|
def _decode_wup_body(body: bytes) -> dict:
|
|
"""解码 WUP 封装 (请求/响应通用) → {servant, func, data}"""
|
|
result = {}
|
|
wup = normalize_wup_payload(_extract_wup(body))
|
|
|
|
# 手动解析 WUP,WSS 大帧常在 tag10 后追加校验/尾部字节
|
|
try:
|
|
ins = TafInputStream(wup)
|
|
|
|
# 读 WUP 字段 tag1~tag10,读到 tag10 后停止(忽略尾部垃圾)
|
|
sBuffer = b""
|
|
while True:
|
|
try:
|
|
tag, dtype = ins.peek_head()
|
|
except EOFError:
|
|
break
|
|
# tag10 是最后一个合法 WUP 字段
|
|
if tag > 10:
|
|
break
|
|
ins.read_head()
|
|
if tag == 1:
|
|
if dtype != TafType.ZERO:
|
|
ins._read_int_value(dtype)
|
|
elif tag in (2, 3):
|
|
if dtype != TafType.ZERO:
|
|
ins._read_int_value(dtype)
|
|
elif tag == 4:
|
|
if dtype != TafType.ZERO:
|
|
ins._read_int_value(dtype)
|
|
elif tag == 5:
|
|
result["servant"] = _decode_taf_value(ins, dtype)
|
|
elif tag == 6:
|
|
result["func"] = _decode_taf_value(ins, dtype)
|
|
elif tag == 7:
|
|
if dtype == TafType.SIMPLE_LIST:
|
|
ins.read_head()
|
|
ln = ins._read_int_len()
|
|
sBuffer = ins.buf.read(ln)
|
|
else:
|
|
ins.skip_field(dtype)
|
|
elif tag == 8:
|
|
if dtype != TafType.ZERO:
|
|
ins._read_int_value(dtype)
|
|
elif tag in (9, 10):
|
|
ins.skip_field(dtype)
|
|
if tag == 10:
|
|
break
|
|
else:
|
|
ins.skip_field(dtype)
|
|
|
|
# 解析 sBuffer → newdata MAP
|
|
if sBuffer:
|
|
try:
|
|
sins = TafInputStream(sBuffer)
|
|
stag, sdt = sins.read_head()
|
|
if stag == 0 and sdt == TafType.MAP:
|
|
cnt = sins._read_int_len()
|
|
for _ in range(cnt):
|
|
_, kt = sins.read_head()
|
|
k = _decode_taf_value(sins, kt)
|
|
_, vt = sins.read_head()
|
|
if vt == TafType.SIMPLE_LIST:
|
|
sins.read_head()
|
|
vln = sins._read_int_len()
|
|
v = sins.buf.read(vln)
|
|
if v:
|
|
try:
|
|
tins = TafInputStream(v)
|
|
ttag, tdt = tins.peek_head()
|
|
if tdt == TafType.STRUCT_BEGIN:
|
|
tins.read_head()
|
|
result[k] = _decode_taf_struct(tins)
|
|
else:
|
|
result[k] = f"<{len(v)}B>"
|
|
except Exception:
|
|
result[k] = f"<{len(v)}B>"
|
|
else:
|
|
result[k] = _decode_taf_value(sins, vt)
|
|
except Exception:
|
|
pass
|
|
except Exception as e:
|
|
result["err"] = str(e)
|
|
return result
|
|
|
|
|
|
def _truncate(obj, max_str=80):
|
|
"""截断过长的值,使日志紧凑"""
|
|
if isinstance(obj, str):
|
|
if len(obj) > max_str:
|
|
return obj[:max_str] + f"...({len(obj)})"
|
|
return obj
|
|
if isinstance(obj, dict):
|
|
return {k: _truncate(v, max_str) for k, v in obj.items()}
|
|
if isinstance(obj, list):
|
|
return [_truncate(v, max_str) for v in obj[:5]]
|
|
return obj
|
|
|
|
|
|
def _fmt_fields(fields: dict) -> str:
|
|
"""把字段字典格式化为一行摘要"""
|
|
parts = []
|
|
for k, v in fields.items():
|
|
if isinstance(v, dict):
|
|
parts.append(f"{k}={{...}}")
|
|
elif isinstance(v, list):
|
|
parts.append(f"{k}=[{len(v)}项]")
|
|
else:
|
|
sv = str(v)
|
|
if len(sv) > 60:
|
|
sv = sv[:60] + "..."
|
|
parts.append(f"{k}={sv}")
|
|
return ", ".join(parts)
|
|
|
|
|
|
def format_wss_log(body: bytes, cmd: int, seq: int, direction: str) -> str:
|
|
"""格式化单条 WSS 帧的日志行
|
|
|
|
Args:
|
|
body: 帧体 (不含 6B 头)
|
|
cmd: 命令字
|
|
seq: 序列号
|
|
direction: "发" 或 "收"
|
|
Returns:
|
|
可读日志字符串
|
|
"""
|
|
prefix = f"[{direction}]"
|
|
|
|
# 心跳精简
|
|
if cmd == 0x10:
|
|
return f"{prefix} HB →"
|
|
if cmd == 0x11:
|
|
return f"{prefix} HB ←"
|
|
|
|
# RPC 请求/响应
|
|
if cmd in (0x03, 0x04):
|
|
decoded = _decode_wup_body(body)
|
|
svc = decoded.get("servant", "")
|
|
func = decoded.get("func", "")
|
|
err = decoded.get("err")
|
|
if err:
|
|
return f"{prefix} RPC ERR: {err}"
|
|
label = f"{svc}.{func}" if svc else "RPC"
|
|
# 找 tRsp/tResp/tReq
|
|
for key in ("tRsp", "tResp", "tReq"):
|
|
data = decoded.get(key)
|
|
if isinstance(data, dict):
|
|
return f"{prefix} {label} {_fmt_fields(_truncate(data))}"
|
|
return f"{prefix} {label}"
|
|
|
|
# AUTH
|
|
if cmd == 0x0A:
|
|
text = _strip_tail(body).decode("utf-8", errors="replace")
|
|
return f"{prefix} AUTH {text[:100]}{'...' if len(text) > 100 else ''}"
|
|
|
|
# REGISTER / CONFIRM / PUSH 等
|
|
clean = _strip_tail(body)
|
|
try:
|
|
ins = TafInputStream(clean)
|
|
# 看第一个 head
|
|
tag, dtype = ins.peek_head()
|
|
if dtype == TafType.STRUCT_BEGIN:
|
|
ins.read_head()
|
|
fields = _decode_taf_struct(ins)
|
|
elif dtype == TafType.MAP:
|
|
ins.read_head()
|
|
cnt = ins._read_int_len()
|
|
fields = {}
|
|
for _ in range(cnt):
|
|
_, kt = ins.read_head()
|
|
k = _decode_taf_value(ins, kt)
|
|
_, vt = ins.read_head()
|
|
v = _decode_taf_value(ins, vt)
|
|
fields[k] = v
|
|
elif dtype == TafType.LIST:
|
|
ins.read_head()
|
|
cnt = ins._read_int_len()
|
|
items = []
|
|
for _ in range(cnt):
|
|
_, it = ins.read_head()
|
|
items.append(_decode_taf_value(ins, it))
|
|
fields = {"items": items}
|
|
else:
|
|
fields = {}
|
|
cmd_name = CMD_NAMES.get(cmd, f"0x{cmd:02x}")
|
|
if fields:
|
|
return f"{prefix} {cmd_name} {_fmt_fields(cast(dict[str, Any], _truncate(fields)))}"
|
|
return f"{prefix} {cmd_name}"
|
|
except Exception:
|
|
cmd_name = CMD_NAMES.get(cmd, f"0x{cmd:02x}")
|
|
return f"{prefix} {cmd_name} ({len(body)}B)"
|