Skip to content

Commit 15867c5

Browse files
Copilotbofh69
andauthored
Add tests for NDEF parsing methods (#34)
* Initial plan * Add comprehensive tests for decode_cc and get_ndef methods Co-authored-by: bofh69 <1444315+bofh69@users.noreply.github.com> * Refactor tests to use pytest fixtures Co-authored-by: bofh69 <1444315+bofh69@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bofh69 <1444315+bofh69@users.noreply.github.com>
1 parent 8bf84bb commit 15867c5

File tree

3 files changed

+427
-1
lines changed

3 files changed

+427
-1
lines changed

src/pn5180_tagomatic/iso14443a.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def decode_cc(self, cc: bytes) -> tuple[int, int, int, bool] | None:
288288

289289
mlen = (cc[2]) * 4
290290

291-
is_readonly = bool((cc[3] & 0xF0) == 0xF)
291+
is_readonly = bool((cc[3] & 0xF0) == 0xF0)
292292

293293
return (major, minor, mlen, is_readonly)
294294

tests/test_iso14443a_ndef.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# SPDX-FileCopyrightText: 2026 PN5180-tagomatic contributors
2+
# SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
"""Tests for ISO14443a card NDEF functionality."""
5+
6+
from unittest.mock import MagicMock
7+
8+
import pytest
9+
10+
from pn5180_tagomatic.cards import Iso14443AUniqueId
11+
from pn5180_tagomatic.iso14443a import ISO14443ACard
12+
13+
14+
@pytest.fixture
15+
def iso14443a_card():
16+
"""Create an ISO14443A card instance for testing."""
17+
mock_comm = MagicMock()
18+
uid = Iso14443AUniqueId(bytes([0x01, 0x02, 0x03, 0x04]), bytes([0x08]))
19+
return ISO14443ACard(mock_comm, uid)
20+
21+
22+
def test_iso14443a_decode_cc_valid(iso14443a_card):
23+
"""Test decode_cc with valid capability container."""
24+
# Valid CC: magic byte 0xE1, version 1.0, memory size 12*4=48 bytes, read/write access
25+
cc = bytes([0xE1, 0x10, 0x0C, 0x00])
26+
27+
result = iso14443a_card.decode_cc(cc)
28+
29+
assert result is not None
30+
major, minor, mlen, is_readonly = result
31+
assert major == 1
32+
assert minor == 0
33+
assert mlen == 48 # 12 * 4
34+
assert is_readonly is False
35+
36+
37+
def test_iso14443a_decode_cc_readonly(iso14443a_card):
38+
"""Test decode_cc with readonly access."""
39+
# CC with readonly flag (cc[3] & 0xF0 == 0xF0)
40+
cc = bytes([0xE1, 0x10, 0x0C, 0xF0])
41+
42+
result = iso14443a_card.decode_cc(cc)
43+
44+
assert result is not None
45+
major, minor, mlen, is_readonly = result
46+
assert major == 1
47+
assert minor == 0
48+
assert mlen == 48
49+
assert is_readonly is True
50+
51+
52+
def test_iso14443a_decode_cc_partial_readonly(iso14443a_card):
53+
"""Test decode_cc with partial readonly bits set."""
54+
# CC with some readonly bits but not all (0xF0)
55+
cc = bytes([0xE1, 0x10, 0x0C, 0xE0])
56+
57+
result = iso14443a_card.decode_cc(cc)
58+
59+
assert result is not None
60+
major, minor, mlen, is_readonly = result
61+
assert is_readonly is False # Not all bits set
62+
63+
64+
def test_iso14443a_decode_cc_invalid_magic(iso14443a_card):
65+
"""Test decode_cc with invalid magic byte."""
66+
# Invalid magic byte (should be 0xE1)
67+
cc = bytes([0xE2, 0x10, 0x0C, 0x00])
68+
69+
result = iso14443a_card.decode_cc(cc)
70+
71+
assert result is None
72+
73+
74+
def test_iso14443a_decode_cc_short_input(iso14443a_card):
75+
"""Test decode_cc with input too short."""
76+
# Too short (only 3 bytes, should be at least 4)
77+
cc = bytes([0xE1, 0x10, 0x0C])
78+
79+
with pytest.raises(ValueError):
80+
iso14443a_card.decode_cc(cc)
81+
82+
83+
def test_iso14443a_get_ndef_simple_tlv(iso14443a_card):
84+
"""Test get_ndef with a simple NDEF TLV structure."""
85+
# Memory layout for ISO14443a:
86+
# Bytes 0-11: Not used (12 bytes before CC)
87+
# Bytes 12-15: CC (0xE1, version 1.0, 48 bytes, read/write)
88+
# Bytes 16-17: NDEF TLV (Type=0x03, Length=10)
89+
# Bytes 18-27: NDEF message content
90+
memory = bytearray([0x00] * 12) # First 12 bytes
91+
memory.extend([
92+
0xE1, 0x10, 0x0C, 0x00, # CC at offset 12
93+
0x03, 0x0A, # NDEF TLV at offset 16
94+
0xD1, 0x01, 0x06, 0x54, 0x02, 0x65, 0x6E, 0x68, 0x69, 0x00 # NDEF message
95+
])
96+
# Pad to 48 bytes to match CC
97+
memory.extend([0x00] * (48 - len(memory)))
98+
99+
result = iso14443a_card.get_ndef(memory)
100+
101+
assert result is not None
102+
pos, ndef_bytes = result
103+
assert pos == 18
104+
assert len(ndef_bytes) == 10
105+
assert ndef_bytes == bytes([0xD1, 0x01, 0x06, 0x54, 0x02, 0x65, 0x6E, 0x68, 0x69, 0x00])
106+
107+
108+
def test_iso14443a_get_ndef_long_length(iso14443a_card):
109+
"""Test get_ndef with 3-byte length encoding (length >= 255)."""
110+
# Memory with 3-byte length encoding
111+
memory = bytearray([0x00] * 12) # First 12 bytes
112+
memory.extend([
113+
0xE1, 0x10, 0xFF, 0x00, # CC: 255*4=1020 bytes
114+
0x03, 0xFF, 0x01, 0x00, # NDEF TLV: Type=0x03, Length=0xFF (3-byte), value=256
115+
])
116+
# Add 256 bytes of NDEF data
117+
memory.extend([0xBB] * 256)
118+
# Pad to 1020 bytes to match CC
119+
memory.extend([0x00] * (1020 - len(memory)))
120+
121+
result = iso14443a_card.get_ndef(memory)
122+
123+
assert result is not None
124+
pos, ndef_bytes = result
125+
assert pos == 20
126+
assert len(ndef_bytes) == 256
127+
assert all(b == 0xBB for b in ndef_bytes)
128+
129+
130+
def test_iso14443a_get_ndef_with_null_tlvs(iso14443a_card):
131+
"""Test get_ndef with NULL TLVs before NDEF."""
132+
# Memory with NULL TLVs (0x00) before NDEF
133+
memory = bytearray([0x00] * 12)
134+
memory.extend([
135+
0xE1, 0x10, 0x0C, 0x00, # CC
136+
0x00, 0x00, # NULL TLVs
137+
0x03, 0x05, # NDEF TLV
138+
0x48, 0x65, 0x6C, 0x6C, 0x6F # "Hello"
139+
])
140+
# Pad to 48 bytes to match CC
141+
memory.extend([0x00] * (48 - len(memory)))
142+
143+
result = iso14443a_card.get_ndef(memory)
144+
145+
assert result is not None
146+
pos, ndef_bytes = result
147+
assert pos == 20
148+
assert ndef_bytes == bytes([0x48, 0x65, 0x6C, 0x6C, 0x6F])
149+
150+
151+
def test_iso14443a_get_ndef_terminator_tlv(iso14443a_card):
152+
"""Test get_ndef returns None when encountering terminator TLV before NDEF."""
153+
# Memory with terminator TLV (0xFE) before NDEF
154+
memory = bytearray([0x00] * 12)
155+
memory.extend([
156+
0xE1, 0x10, 0x0C, 0x00, # CC
157+
0xFE, # Terminator TLV
158+
0x03, 0x05, # NDEF TLV (won't be reached)
159+
0x48, 0x65, 0x6C, 0x6C, 0x6F
160+
])
161+
162+
result = iso14443a_card.get_ndef(memory)
163+
164+
assert result is None
165+
166+
167+
def test_iso14443a_get_ndef_invalid_cc(iso14443a_card):
168+
"""Test get_ndef returns None with invalid CC."""
169+
# Invalid CC (wrong magic byte)
170+
memory = bytearray([0x00] * 12)
171+
memory.extend([
172+
0xE2, 0x10, 0x0C, 0x00, # Invalid CC
173+
0x03, 0x05,
174+
0x48, 0x65, 0x6C, 0x6C, 0x6F
175+
])
176+
177+
result = iso14443a_card.get_ndef(memory)
178+
179+
assert result is None
180+
181+
182+
def test_iso14443a_get_ndef_unsupported_version(iso14443a_card):
183+
"""Test get_ndef returns None with unsupported major version."""
184+
# Major version > 1 (unsupported for ISO14443a)
185+
memory = bytearray([0x00] * 12)
186+
memory.extend([
187+
0xE1, 0x20, 0x0C, 0x00, # CC with major version 2
188+
0x03, 0x05,
189+
0x48, 0x65, 0x6C, 0x6C, 0x6F
190+
])
191+
192+
result = iso14443a_card.get_ndef(memory)
193+
194+
assert result is None
195+
196+
197+
def test_iso14443a_get_ndef_memory_too_small(iso14443a_card):
198+
"""Test get_ndef returns None when memory is smaller than CC indicates."""
199+
# CC indicates 1020 bytes but memory is only 28 bytes
200+
memory = bytearray([0x00] * 12)
201+
memory.extend([
202+
0xE1, 0x10, 0xFF, 0x00, # CC: 255*4=1020 bytes
203+
0x03, 0x05,
204+
0x48, 0x65, 0x6C, 0x6C, 0x6F
205+
])
206+
207+
result = iso14443a_card.get_ndef(memory)
208+
209+
assert result is None
210+
211+
212+
def test_iso14443a_get_ndef_field_exceeds_memory(iso14443a_card):
213+
"""Test get_ndef returns None when NDEF field exceeds memory length."""
214+
# NDEF field length exceeds available memory
215+
memory = bytearray([0x00] * 12)
216+
memory.extend([
217+
0xE1, 0x10, 0x0C, 0x00, # CC: 48 bytes
218+
0x03, 0x64, # NDEF TLV: Length=100 (exceeds limit)
219+
0x48, 0x65, 0x6C, 0x6C, 0x6F
220+
])
221+
222+
result = iso14443a_card.get_ndef(memory)
223+
224+
assert result is None

0 commit comments

Comments
 (0)