fix(huya): 结构体尾部字段容错解析(服务端新增 ZERO 字段不再打挂整个 RPC)

真机现象(2026-10-15,外部 CK 导入后查询角色):checkUserBindGameAccount 响应结构
尾部多了 tag6(ZERO 类型 0x6c),共享的 read_struct 读到非 STRUCT_END 直接抛
「期望 STRUCT_END, 实际 0x0c」,整个 RPC 返回 None —— 工作台「查询角色」永远失败。

- taf_protocol:read_struct 收尾改为按 TAF 语义跳过未建模的尾部字段(上限 32 个,
  防止解析错位时无限吞字节);新增 skip_to_struct_end()
- frame_decoder / shop_structs:抽出 decode_struct_bytes 与 ShopRawRsp.from_payload,
  WSS 与 HTTP 两条通道共用一份通用解码逻辑(wss_client 同步改用)

验证:tests/test_huya_struct_parse_resilience.py 用真机响应字节回归(含 tag6=ZERO 的
响应能解析出角色「泪雨全是谎言」);按 AGENTS.md 反向验证 —— 注掉
skip_to_struct_end() 即复现线上报错「期望 STRUCT_END, 实际 0x0c」,还原后恢复。
全量 pytest 通过。
This commit is contained in:
yml2213
2026-10-15 15:21:34 +08:00
parent 4ce30e5c26
commit a9d711a7e9
5 changed files with 133 additions and 29 deletions
+18
View File
@@ -1128,6 +1128,24 @@ class ShopRawRsp:
node = node.get(key)
return node
@classmethod
def from_payload(cls, payload: dict | None) -> "ShopRawRsp":
"""从通用解码结果构造:code/message 支持扁平与嵌套(tag0.tag0/tag0.tag1)两种形态。"""
data = payload or {}
code = 0
message = ""
head = data.get("tag0")
if isinstance(head, int):
code = int(head)
if isinstance(data.get("tag1"), str):
message = data["tag1"]
elif isinstance(head, dict):
if isinstance(head.get("tag0"), int):
code = int(head["tag0"])
if isinstance(head.get("tag1"), str):
message = head["tag1"]
return cls(code=code, message=message, payload=data)
def to_dict(self) -> dict:
return {"code": self.code, "message": self.message, "payload": self.payload}