"""14a sim memory sync: mfu_dump_t builder, EML_MEMSET push, config-page safety. Covers the P0 fix — loading a Type 2 (NTAG/Ultralight) model's memory into the firmware emulator so the reader sees real page data (UID/CC/NDEF/PWD/PACK). """ import struct from unittest.mock import MagicMock, patch from pm3py.sim import NTAG213, NTAG216, ndef_uri from pm3py.transponders.hf.iso14443a.ndef import ( MFU_DUMP_PREFIX_LENGTH, CMD_HF_MIFARE_EML_MEMSET, PAGE_SIZE, ) def test_mfu_dump_layout(): t = NTAG213() t.set_ndef(ndef_uri("https://dngr.us/rick")) img = t._build_mfu_dump() assert len(img) == MFU_DUMP_PREFIX_LENGTH + t._total_pages * PAGE_SIZE assert img[0:8] == t._version_bytes # version[8] assert img[11] == t._total_pages - 1 # pages = last page index assert img[12:44] == bytes(t._signature)[:32].ljust(32, b"\x00") # signature[32] # page data begins at offset 56 and mirrors _memory assert img[MFU_DUMP_PREFIX_LENGTH:] == bytes(t._memory) # NDEF TLV present at page 4 assert img[MFU_DUMP_PREFIX_LENGTH + 4 * PAGE_SIZE] == 0x03 def test_set_ndef_preserves_config_pages(): """set_ndef must not wipe the lock/config/PWD/PACK pages above the NDEF area.""" t = NTAG213() assert t.get_page(0x2B) == b"\xff\xff\xff\xff" # PWD default assert t.get_page(0x29)[3] == 0xFF # CFG0 AUTH0 t.set_ndef(ndef_uri("https://dngr.us/rick")) assert t.get_page(0x2B) == b"\xff\xff\xff\xff" # PWD survived assert t.get_page(0x29)[3] == 0xFF # AUTH0 survived assert t.get_page(4)[0] == 0x03 # NDEF TLV written def test_clear_ndef_preserves_config_pages(): t = NTAG213() t.set_ndef(ndef_uri("https://dngr.us/rick")) t.clear_ndef() assert t.get_page(0x2B) == b"\xff\xff\xff\xff" # PWD still intact assert t.get_page(4) == b"\x00\x00\x00\x00" # NDEF area cleared def _capture_eml_writes(tag): """Run tag.sync() with a mock serial; return list of (cmd, payload).""" captured = [] def fake_encode(cmd, payload=b""): captured.append((cmd, payload)) return b"" tag._serial = MagicMock() with patch("pm3py.core.transport.encode_ng_frame", side_effect=fake_encode): tag.sync() return captured def test_sync_pushes_full_image_via_eml_memset(): t = NTAG213() t.set_ndef(ndef_uri("https://dngr.us/rick")) image = t._build_mfu_dump() frames = _capture_eml_writes(t) assert frames, "sync() wrote no frames" reassembled = bytearray() for cmd, payload in frames: assert cmd == CMD_HF_MIFARE_EML_MEMSET blockno, blockcnt, blockwidth = struct.unpack_from(" multiple EML_MEMSET.""" t = NTAG216() frames = _capture_eml_writes(t) assert len(frames) >= 2 # every chunk stays within the NG payload cap for _cmd, payload in frames: assert len(payload) <= 512 def test_sync_requires_live_session(): import pytest t = NTAG213() t._serial = None with pytest.raises(RuntimeError, match="live session"): t.sync()