hf.iso14a.raw() returned the entire receive buffer the firmware replies with (sizeof(buf)), so a tag that didn't answer came back as a run of zeros (~40 bytes) rather than empty. The firmware puts the real received length in oldarg[0]; trim resp.data to it. No response -> empty (raw=b"", data=None); adds a `received` count. 2 regression tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
3.9 KiB
Python
94 lines
3.9 KiB
Python
import struct
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
from pm3py.core.protocol import Cmd
|
|
from pm3py.core.transport import PM3Response
|
|
from pm3py.core.hf_iso14a import HF14ACommands
|
|
|
|
def test_14a_scan():
|
|
t = AsyncMock()
|
|
hf14a = HF14ACommands(t)
|
|
# Build iso14a_card_select_t response
|
|
uid = b"\x04\x01\x02\x03\x04\x05\x06"
|
|
card = uid + b"\x00" * 3 # uid[10]
|
|
card += bytes([7]) # uidlen
|
|
card += b"\x44\x00" # atqa
|
|
card += bytes([0x08]) # sak
|
|
card += bytes([0]) # ats_len
|
|
card += b"\x00" * 256 # ats
|
|
resp = PM3Response(cmd=Cmd.ACK, status=0, reason=0, ng=False,
|
|
data=card, oldarg=[1, 0, 0])
|
|
t.send_mix.return_value = resp
|
|
result = asyncio.get_event_loop().run_until_complete(hf14a.scan())
|
|
assert result["uid"] == "04010203040506"
|
|
assert result["uid_len"] == 7
|
|
assert result["sak"] == 0x08
|
|
|
|
|
|
def test_14a_raw_trims_to_received_length():
|
|
t = AsyncMock()
|
|
hf14a = HF14ACommands(t)
|
|
# firmware replies with its whole (padded) buffer; oldarg[0] = bytes actually received
|
|
version = bytes.fromhex("0004040201000f03") + b"\x80\x91" # 8-byte version + 2-byte CRC
|
|
t.send_mix.return_value = PM3Response(cmd=Cmd.ACK, status=0, reason=0, ng=False,
|
|
data=version + b"\x00" * 30, oldarg=[10, 0, 0])
|
|
result = asyncio.get_event_loop().run_until_complete(hf14a.raw(b"\x60"))
|
|
assert result["received"] == 10
|
|
assert result["raw"] == version # trimmed to the received length, not the padding
|
|
assert result["data"] == version.hex()
|
|
|
|
|
|
def test_14a_raw_no_response_is_empty():
|
|
t = AsyncMock()
|
|
hf14a = HF14ACommands(t)
|
|
# tag didn't answer: received length 0, buffer full of zeros -> must come back empty
|
|
t.send_mix.return_value = PM3Response(cmd=Cmd.ACK, status=0, reason=0, ng=False,
|
|
data=b"\x00" * 40, oldarg=[0, 0, 0])
|
|
result = asyncio.get_event_loop().run_until_complete(hf14a.raw(b"\x30\x99"))
|
|
assert result["received"] == 0
|
|
assert result["raw"] == b"" # not 40 bytes of zeros
|
|
assert result["data"] is None
|
|
|
|
|
|
def test_14a_sim_packs_ng_struct():
|
|
t = AsyncMock()
|
|
hf14a = HF14ACommands(t)
|
|
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SIMULATE, status=0,
|
|
reason=0, ng=True, data=b"", oldarg=[])
|
|
uid = b"\x04\x01\x02\x03"
|
|
result = asyncio.get_event_loop().run_until_complete(
|
|
hf14a.sim(tagtype=7, uid=uid, exit_after=3))
|
|
assert result["status"] == 0
|
|
args, kwargs = t.send_ng.call_args
|
|
assert args[0] == Cmd.HF_ISO14443A_SIMULATE
|
|
payload = kwargs["payload"]
|
|
assert len(payload) == 69 # PACKED firmware struct size
|
|
tagtype, flags = struct.unpack_from("<BH", payload, 0)
|
|
assert tagtype == 7
|
|
assert flags & 0x0030 == 0x0010 # FLAG_4B_UID_IN_DATA
|
|
assert payload[3:7] == uid # uid[10] field, first 4 bytes
|
|
assert payload[13] == 3 # exitAfter
|
|
|
|
|
|
def test_14a_sim_no_uid_uses_emul_flag():
|
|
t = AsyncMock()
|
|
hf14a = HF14ACommands(t)
|
|
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SIMULATE, status=0,
|
|
reason=0, ng=True, data=b"", oldarg=[])
|
|
asyncio.get_event_loop().run_until_complete(hf14a.sim(tagtype=1))
|
|
payload = t.send_ng.call_args.kwargs["payload"]
|
|
_, flags = struct.unpack_from("<BH", payload, 0)
|
|
assert flags & 0x0030 == 0x0000 # FLAG_UID_IN_EMUL
|
|
|
|
|
|
def test_14a_sniff_sends_ng_param_byte():
|
|
t = AsyncMock()
|
|
hf14a = HF14ACommands(t)
|
|
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO14443A_SNIFF, status=0,
|
|
reason=0, ng=True, data=b"", oldarg=[])
|
|
result = asyncio.get_event_loop().run_until_complete(hf14a.sniff(2))
|
|
assert result["status"] == 0
|
|
args, kwargs = t.send_ng.call_args
|
|
assert args[0] == Cmd.HF_ISO14443A_SNIFF
|
|
assert kwargs["payload"] == bytes([2])
|