"""虎牙新设备注册链 (core/huya/dfp_register) 测试。 覆盖: * 随机 dfpReport body/cw 结构 (4226B / 4146B / 固定10B尾); * dfpReport 响应解析 (t1/t2/t5) 及缺字段失败; * register_device 全流程: 三步 POST 顺序与 content-type、fingerprint 注入、 注册结果三元组返回; * 失败语义: 模板缺失/HTTP 异常 -> DfpRegistrationError (不静默、不发登录帧)。 """ import hashlib from unittest.mock import patch import pytest import requests from core.huya.dfp_register import ( CW_LEN, SELECT_OPERATOR_SDID, DfpRegistrationError, _build_dfp_json_plain, _build_get_dfp_config_request, _build_random_dfp_body, _build_select_operator_request, _parse_response, register_device, ) from core.huya.wup_protocol import WupResponse TAIL = bytes.fromhex("3600400c0b8c980ca80c") def _fake_resp() -> bytes: """合成 dfpReport 响应: t1(32hex) + t2(180B base64 action) + t5(40hex)。""" return b"\x16\x20" + b"a" * 32 + b"\x26\xb4" + b"A" * 180 + b"\x56\x28" + b"b" * 40 _GOLDEN_FP = b"02df398797432eadefcc12767119ad5e80999389" def _dummy_chain() -> dict: # selectOperator 请求体包含金样本 fingerprint, 供替换注入测试 return { "getDfpConfig": (b"cfg-req", b"cfg-resp"), "selectOperator": (b"sel-hdr" + _GOLDEN_FP + b"sel-tail", b"sel-resp"), "dfpReport": (b"dfp-req", b"dfp-resp"), } class TestRandomDfpBody: def test_body_shape(self): body = _build_random_dfp_body() assert len(body) == 4226 cw = body[-CW_LEN:] assert len(cw) == CW_LEN # 4146 assert cw.endswith(TAIL) def test_body_is_randomized(self): b1 = _build_random_dfp_body() b2 = _build_random_dfp_body() assert b1 != b2, "每次注册应生成不同的随机 cw" def test_body_uses_device_profile_fields(self): profile = { "app_version": "13.4.22", "vendor": "vivo", "model": "V2370A", "screen": "V2370A,34,13", "width": "1080", "height": "2412", "device_id": "d" * 64, "guid32": "g" * 64, } with ( patch( "core.huya.dfp_register._random_triple", return_value=("h" * 64, "r" * 64, "k" * 64), ), ): plaintext = _build_dfp_json_plain(profile) assert b'"channel":"vivo"' in plaintext assert b'"deviceName":"V2370A"' in plaintext assert b'"deviceId":"' + b"d" * 64 in plaintext assert b'"systemVer":"V2370A,34,13"' in plaintext with ( patch( "core.huya.dfp_register._random_triple", return_value=("h" * 64, "r" * 64, "k" * 64), ), patch( "core.huya.dfp_register.os.urandom", side_effect=lambda n: b"\0" * n ), ): body = _build_random_dfp_body(profile) assert hashlib.sha256(plaintext).digest() in body[-CW_LEN + 2 :] class TestParseResponse: def test_parse_ok(self): t1, t2, t5 = _parse_response(_fake_resp()) assert t1 == "a" * 32 assert t2 == "A" * 180 assert t5 == "b" * 40 def test_parse_missing_fields_raises(self): with pytest.raises(DfpRegistrationError): _parse_response(b"\x16\x20" + b"a" * 32) # 缺 t2/t5 with pytest.raises(DfpRegistrationError): _parse_response(b"\x26\xb4" + b"A" * 180) # 缺 t1/t5 with pytest.raises(DfpRegistrationError): _parse_response(b"\x56\x28" + b"b" * 40) # 缺 t1/t2 class TestRegisterDevice: def test_get_dfp_config_request_is_generated(self): req = _build_get_dfp_config_request() assert len(req) == 67 wup = WupResponse() wup.decode(req) assert wup.sServantName == "huyaudbwebui" assert wup.sFuncName == "getDfpConfig" assert wup.readStruct("tReq") == bytes.fromhex("0a0604353030380b") def test_select_operator_request_is_generated(self): fp = "0" * 40 req = _build_select_operator_request(fp) assert len(req) == 419 assert fp.encode() in req assert SELECT_OPERATOR_SDID.encode() in req wup = WupResponse() wup.decode(req) assert wup.sServantName == "huyaudbwebui" assert wup.sFuncName == "selectOperator" assert "_wup_data" in wup.newdata def test_flow_three_steps_and_returns_triple(self): calls = [] def spy_post( body, content_type="application/octet-stream", timeout=20, proxies=None ): calls.append(content_type) return _fake_resp() with ( patch("core.huya.dfp_register._load_chain", return_value=_dummy_chain()), patch("core.huya.dfp_register._post", side_effect=spy_post), ): t1, t2, t5 = register_device(fingerprint=None) assert (t1, t2, t5) == ("a" * 32, "A" * 180, "b" * 40) # getDfpConfig -> selectOperator -> dfpReport 的 content-type 序列 assert calls == [ "application/octet-stream", "application/x-wup", "application/octet-stream", ] def test_select_operator_injects_fingerprint(self): captured = [] def spy_post( body, content_type="application/octet-stream", timeout=20, proxies=None ): captured.append((body, content_type)) return _fake_resp() new_fp = b"0" * 20 + b"f" * 20 with ( patch("core.huya.dfp_register._load_chain", return_value=_dummy_chain()), patch("core.huya.dfp_register._post", side_effect=spy_post), ): register_device(fingerprint=new_fp.decode("ascii")) sel_body, sel_ct = captured[1] assert sel_ct == "application/x-wup" assert new_fp in sel_body, "selectOperator 应注入当前账号画像 fingerprint" assert _GOLDEN_FP not in sel_body # dfpReport 请求体为随机 cw 结构 (零设备生成) dfp_body, dfp_ct = captured[2] assert len(dfp_body) == 4226 assert dfp_ct == "application/octet-stream" def test_http_failure_raises(self): """HTTP 层异常由 _post 转换为 DfpRegistrationError 后向上传播 (不静默)。""" with ( patch("core.huya.dfp_register._load_chain", return_value=_dummy_chain()), patch( "core.huya.dfp_register.requests.post", side_effect=requests.RequestException("connection reset"), ), pytest.raises(DfpRegistrationError), ): register_device()