feat(lf): test-mode T55xx write (opcode 01) for recovery
Add a `test` flag to lf.t55.writebl that sets the firmware test-mode bit, sending the datasheet's opcode-01 reconfiguration write (§5.10.3) instead of the standard opcode-10 write. This is the path that can rewrite a tag a normal write can't (corrupted/locked config), when the master key still permits it. Matches the C client's `lf t55 write -t`. Default off; routine writes are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -25,8 +25,15 @@ class T55xxCommands:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def writebl(self, block: int, data: int, page: int = 0,
|
async def writebl(self, block: int, data: int, page: int = 0,
|
||||||
password: int | None = None, downlink_mode: int = 0) -> dict:
|
password: int | None = None, downlink_mode: int = 0,
|
||||||
"""Write a T55xx block."""
|
test: bool = False) -> dict:
|
||||||
|
"""Write a T55xx block.
|
||||||
|
|
||||||
|
``test`` uses the test-mode write (downlink opcode 01) instead of the standard opcode 10.
|
||||||
|
Test mode is the datasheet's reconfiguration path (§5.10.3) — it can rewrite a tag that a
|
||||||
|
normal write can't (a corrupted/locked config), provided the master key still permits it
|
||||||
|
(allowed for key 9, denied once key 6 is set). Use it for recovery, not routine writes.
|
||||||
|
"""
|
||||||
pwd = password if password is not None else 0
|
pwd = password if password is not None else 0
|
||||||
# Firmware t55xx_write_block_t: data(4)+pwd(4)+blockno(1)+flags(1).
|
# Firmware t55xx_write_block_t: data(4)+pwd(4)+blockno(1)+flags(1).
|
||||||
# flags: 0x01 PwdMode, 0x02 Page, 0x04 test, 0x18 downlink_mode<<3.
|
# flags: 0x01 PwdMode, 0x02 Page, 0x04 test, 0x18 downlink_mode<<3.
|
||||||
@@ -35,6 +42,8 @@ class T55xxCommands:
|
|||||||
flags |= 0x01
|
flags |= 0x01
|
||||||
if page:
|
if page:
|
||||||
flags |= 0x02
|
flags |= 0x02
|
||||||
|
if test:
|
||||||
|
flags |= 0x04
|
||||||
flags |= (downlink_mode & 0x03) << 3
|
flags |= (downlink_mode & 0x03) << 3
|
||||||
payload = struct.pack("<IIBB", data, pwd, block, flags)
|
payload = struct.pack("<IIBB", data, pwd, block, flags)
|
||||||
resp = await self._t.send_ng(Cmd.LF_T55XX_WRITEBL, payload, timeout=5.0)
|
resp = await self._t.send_ng(Cmd.LF_T55XX_WRITEBL, payload, timeout=5.0)
|
||||||
|
|||||||
@@ -57,6 +57,17 @@ def test_lf_t55_writebl():
|
|||||||
# PwdMode(0x01) | Page(0x02) | downlink 2<<3 (0x10) = 0x13
|
# PwdMode(0x01) | Page(0x02) | downlink 2<<3 (0x10) = 0x13
|
||||||
assert flags == 0x13
|
assert flags == 0x13
|
||||||
|
|
||||||
|
def test_lf_t55_writebl_testmode():
|
||||||
|
t = AsyncMock()
|
||||||
|
lf = LFCommands(t)
|
||||||
|
t.send_ng.return_value = make_response(Cmd.LF_T55XX_WRITEBL, 0, b"")
|
||||||
|
asyncio.get_event_loop().run_until_complete(
|
||||||
|
lf.t55.writebl(block=0, data=0x00148040, test=True))
|
||||||
|
_, _, blockno, flags = struct.unpack("<IIBB", t.send_ng.call_args.args[1])
|
||||||
|
assert blockno == 0
|
||||||
|
assert flags & 0x04 # test-mode bit set (downlink opcode 01)
|
||||||
|
assert not (flags & 0x01) # no password mode by default
|
||||||
|
|
||||||
def test_lf_t55_wakeup():
|
def test_lf_t55_wakeup():
|
||||||
t = AsyncMock()
|
t = AsyncMock()
|
||||||
lf = LFCommands(t)
|
lf = LFCommands(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user