feat(ppclock): 离线激活 keygen(ror2 公式+EFEF 反解 MAC)+ activate --auto;docs 终稿(firmware-analysis/protocol/PROGRESS),132 测试全绿
This commit is contained in:
1 parent
bed8973551
commit
6615696c2d
8 files changed
+465
-24
No files matched your search
@@ -206,3 +206,28 @@ class TestOta:
|
||||
code, out = run_cli(capsys, ["--json", "ota", str(bad)])
|
||||
assert code == 1
|
||||
assert out["ok"] is False
|
||||
|
||||
|
||||
class TestActivateAuto:
|
||||
def test_activate_auto_via_device_id(self, capsys):
|
||||
"""EFEF 应答 ID '89794900980101' → MAC 9A:01:99:50:10:20 → keygen → EF 下发"""
|
||||
class IdTransport(FakeTransport):
|
||||
async def request_device_id(self, timeout=8.0):
|
||||
self.rxtx_writes.append(bytes([0xEF, 0xEF]))
|
||||
return "89794900980101"
|
||||
FakeTransport.instances.clear()
|
||||
import ppclock.cli as c
|
||||
orig = c._make_transport
|
||||
c._make_transport = lambda args: IdTransport(args.mac, args.timeout)
|
||||
try:
|
||||
code, out = run_cli(capsys, ["--json", "activate", "--auto"])
|
||||
finally:
|
||||
c._make_transport = orig
|
||||
assert code == 0
|
||||
t = IdTransport.instances[0]
|
||||
assert t.rxtx_writes[0] == bytes([0xEF, 0xEF])
|
||||
# keygen(9A,01,99,50,10,20):逐字节 rol2(((m<<1)&FF)^EF)%101
|
||||
from ppclock.protocol import keygen_code
|
||||
expected = keygen_code(bytes([0x9A, 0x01, 0x99, 0x50, 0x10, 0x20]))
|
||||
assert t.rxtx_writes[1] == bytes([0xEF]) + expected
|
||||
assert out["data"]["code"] == expected.hex()
|
||||
@@ -0,0 +1,49 @@
|
||||
"""激活 keygen 测试 — 向量按 docs/firmware-analysis.md §4.2 手工推算。"""
|
||||
import pytest
|
||||
|
||||
from ppclock import protocol as P
|
||||
|
||||
|
||||
class TestKeygen:
|
||||
def test_keygen_vector(self):
|
||||
"""MAC 00:11:22:33:44:55 → [49,14,32,98,15,81](ror2(((m<<1)&FF)^EF)%101 手算)"""
|
||||
code = P.keygen_code(bytes([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]))
|
||||
assert list(code) == [49, 14, 32, 98, 15, 81]
|
||||
|
||||
def test_keygen_all_zero_mac(self):
|
||||
# t = 0^0xEF = 0xEF; ror2(0xEF)=0xFB=251; 251%101=49
|
||||
assert list(P.keygen_code(bytes(6))) == [49] * 6
|
||||
|
||||
def test_keygen_all_ff_mac(self):
|
||||
# t = (0x1FE & 0xFF)^0xEF = 0xFE^0xEF = 0x11; rol2(0x11)=0x44=68; 68%101=68
|
||||
assert list(P.keygen_code(b"\xFF" * 6)) == [68] * 6
|
||||
|
||||
def test_keygen_requires_6_bytes(self):
|
||||
with pytest.raises(ValueError):
|
||||
P.keygen_code(b"\x01" * 5)
|
||||
|
||||
|
||||
class TestRecoverMac:
|
||||
def test_recover_vector(self):
|
||||
"""MAC [9A,01,99,50,10,20] → ID '89794900980101'(|MAC4-99|,|MAC5-99|,|MAC3-99|,
|
||||
|MAC2-99|,|MAC1-99|,|MAC0-99|,flags;flags=各字节≥0x9A 位掩码)"""
|
||||
mac = P.recover_mac_from_device_id("89794900980101")
|
||||
assert list(mac) == [0x9A, 0x01, 0x99, 0x50, 0x10, 0x20]
|
||||
|
||||
def test_roundtrip_all_high(self):
|
||||
# 全 ≥0x9A:flags=0x3F;v=|m-0x99|
|
||||
mac = bytes([0x9A, 0xB0, 0xC1, 0xFF, 0xA0, 0x9B])
|
||||
vals = [abs(mac[4] - 0x99), abs(mac[5] - 0x99), abs(mac[3] - 0x99),
|
||||
abs(mac[2] - 0x99), abs(mac[1] - 0x99), abs(mac[0] - 0x99)]
|
||||
dev_id = "".join(f"{v:02X}" for v in vals) + "3F"
|
||||
assert P.recover_mac_from_device_id(dev_id) == mac
|
||||
|
||||
def test_id_must_be_14_hex(self):
|
||||
with pytest.raises(ValueError):
|
||||
P.recover_mac_from_device_id("123")
|
||||
|
||||
def test_keygen_after_recover_matches(self):
|
||||
"""recovered MAC → keygen:与直接对原 MAC keygen 一致"""
|
||||
mac = bytes([0x9A, 0x01, 0x99, 0x50, 0x10, 0x20])
|
||||
recovered = P.recover_mac_from_device_id("89794900980101")
|
||||
assert P.keygen_code(recovered) == P.keygen_code(mac)
|
||||
Reference in new issue
Block a user