import asyncio import struct from unittest.mock import AsyncMock from pm3py.protocol import Cmd from pm3py.transport import PM3Response from pm3py.hf_15 import ( HF15Commands, parse_tracelog, TRACELOG_HDR_SIZE, decode_15693_request, decode_15693_response, decode_15693, format_sniff_line, decode_ndef_annotation, ) def test_15_scan(): t = AsyncMock() iso15 = HF15Commands(t) # Fake response: flags(1) + DSFID(1) + UID(8) resp_data = bytes([0x00, 0x00]) + bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xE0]) t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO15693_COMMAND, status=0, reason=0, ng=True, data=resp_data) result = asyncio.get_event_loop().run_until_complete(iso15.scan()) assert "uid" in result # ---- Tracelog parsing ---- def _make_tracelog_entry(timestamp, duration, is_response, data): """Build a raw tracelog entry matching firmware's tracelog_hdr_t.""" data_len_flags = len(data) | (0x8000 if is_response else 0) hdr = struct.pack("> 8) & 0xFF, len(rec) & 0xFF]) + rec + bytes([0xFE]) ann = decode_ndef_annotation(tlv) assert ann is not None assert "..." in ann class TestResponseNdefDecode: def test_read_single_block_0_cc(self): data = bytes([0x00, 0xE1, 0x40, 0x0D, 0x01]) ann = decode_15693_response(data) assert "CC" in ann assert "MBREAD" in ann def test_read_multiple_blocks_ndef(self): msg = bytes([0xD1, 0x01, 0x07, 0x54, 0x02, 0x65, 0x6E, 0x74, 0x65, 0x73, 0x74]) tlv = bytes([0x03, len(msg)]) + msg + bytes([0xFE]) data = bytes([0x00]) + tlv + bytes(50) ann = decode_15693_response(data) assert '"test"' in ann def test_read_multiple_with_cc_and_ndef(self): cc = bytes([0xE1, 0x40, 0x0D, 0x01]) msg = bytes([0xD1, 0x01, 0x07, 0x54, 0x02, 0x65, 0x6E, 0x74, 0x65, 0x73, 0x74]) tlv = bytes([0x03, len(msg)]) + msg + bytes([0xFE]) data = bytes([0x00]) + cc + tlv + bytes(40) ann = decode_15693_response(data) assert "CC" in ann assert '"test"' in ann def test_generic_data_no_ndef(self): data = bytes([0x00, 0xDE, 0xAD, 0xBE, 0xEF]) ann = decode_15693_response(data) assert "OK [4B]" in ann def test_inventory_not_affected(self): data = bytes([0x00, 0x00]) + bytes(8) ann = decode_15693_response(data) assert "INVENTORY" in ann class TestSniffLineAnnotationOverflow: def test_long_annotation_wraps(self): cc = bytes([0xE1, 0x40, 0x0D, 0x01]) # Build NDEF text record manually (no sim.type5 dependency) text = "A" * 30 lang = b"en" payload = bytes([len(lang)]) + lang + text.encode("utf-8") msg = bytes([0xD1, 0x01, len(payload), 0x54]) + payload tlv = bytes([0x03, len(msg)]) + msg + bytes([0xFE]) payload = bytes([0x00]) + cc + tlv + bytes([0xAA, 0xBB]) entry = {"is_response": True, "data": payload, "timestamp": 0, "duration": 0, "parity": b""} line = format_sniff_line(entry, width=80, is_tty=False) lines = line.split("\n") ann_lines = [l for l in lines if "NDEF" in l] assert len(ann_lines) >= 1 def test_short_annotation_no_wrap(self): data = bytes([0x00, 0xE1, 0x40, 0x0D, 0x01, 0xAA, 0xBB]) entry = {"is_response": True, "data": data, "timestamp": 0, "duration": 0, "parity": b""} line = format_sniff_line(entry, width=120, is_tty=False) assert "CC" in line