feat(rawcli): full MIFARE Classic datasheet command set + value blocks
Completes the MIFARE Classic catalog with its datasheet wire commands (the standing rule: every transponder exposes its full datasheet command set, like NTAG21x), each carrying its wire opcode so it autocompletes by name and by the hex/binary opcode value, with the block memory map: AUTH_A 0x60 / AUTH_B 0x61 auth a sector (test whether a key works; auth is otherwise implicit) READ 0x30 / WRITE 0xA0 (existing) INCREMENT 0xC1 / DECREMENT 0xC0 / RESTORE 0xC2 / TRANSFER 0xB0 value-block ops PERSONALIZE_UID 0x40 / SET_MOD_TYPE 0x43 EV1 (opcode + hint; execution not yet wired) CHK (key finder) / HALT 0x50 (existing) core: hf.mf.value(block, action, value, transfer_block, key, key_type) wires CMD_HF_MIFARE_VALUE (0x0627) — payload mirrors the stock client's CmdHF14AMfValue / firmware MifareValue (key[0:6], action[9], transferBlk[10], operand[11:15], transfer-key[27:33], nested-auth flag[33]); the op is committed to transfer_block, or in place when None; a cross-sector transfer sets the nested-auth flag. INCREMENT/DECREMENT/RESTORE commit in place; TRANSFER copies a value block block->dest. No completer/memory/app changes — the machinery is data-driven off the catalog. Hardware-verified on a MIFARE Classic 1K: identify; c1/60/b0 (and binary) surface INCREMENT/AUTH_A /TRANSFER with the block map; AUTH_A reports the right key ok and a wrong key failed; a value round-trip INCREMENTed 100 -> 105 with the value-block format intact (block restored after). 1370 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,40 @@ def test_mf_wrbl():
|
||||
assert kwargs["arg1"] == 0
|
||||
|
||||
|
||||
def test_mf_value_increment_in_place():
|
||||
t = AsyncMock()
|
||||
mf = HFMFCommands(t)
|
||||
t.send_mix.return_value = PM3Response(cmd=Cmd.ACK, status=0, reason=0,
|
||||
ng=False, data=b"", oldarg=[1, 0, 0])
|
||||
result = asyncio.get_event_loop().run_until_complete(
|
||||
mf.value(block=5, action=0, value=10, key="FFFFFFFFFFFF"))
|
||||
assert result == {"success": True, "block": 5} # in-place commit
|
||||
args, kwargs = t.send_mix.call_args.args, t.send_mix.call_args.kwargs
|
||||
assert args[0] == Cmd.HF_MIFARE_VALUE
|
||||
assert kwargs["arg0"] == 5 and kwargs["arg1"] == 0 and kwargs["arg2"] == 0
|
||||
p = kwargs["payload"]
|
||||
assert len(p) == 34
|
||||
assert p[:6] == bytes.fromhex("FFFFFFFFFFFF") # key
|
||||
assert p[9] == 0 # action = increment
|
||||
assert p[10] == 0 # transfer in place (commit to block)
|
||||
assert struct.unpack_from("<i", p, 11)[0] == 10 # operand
|
||||
assert p[33] == 0 # same sector -> no nested auth
|
||||
|
||||
|
||||
def test_mf_value_transfer_cross_sector():
|
||||
t = AsyncMock()
|
||||
mf = HFMFCommands(t)
|
||||
t.send_mix.return_value = PM3Response(cmd=Cmd.ACK, status=0, reason=0,
|
||||
ng=False, data=b"", oldarg=[1, 0, 0])
|
||||
# block 4 (sector 1) restore -> block 8 (sector 2): cross-sector => nested-auth flag set
|
||||
result = asyncio.get_event_loop().run_until_complete(
|
||||
mf.value(block=4, action=2, transfer_block=8, key="A0A1A2A3A4A5"))
|
||||
assert result == {"success": True, "block": 8}
|
||||
p = t.send_mix.call_args.kwargs["payload"]
|
||||
assert p[9] == 2 and p[10] == 8 # restore, transfer to block 8
|
||||
assert p[33] == 1 # crosses sector -> nested auth
|
||||
|
||||
|
||||
def test_mf_nested():
|
||||
t = AsyncMock()
|
||||
mf = HFMFCommands(t)
|
||||
|
||||
Reference in New Issue
Block a user