Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion examples/iso_14443-read-memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def main() -> int:
print(f"Error: {e}")
return 1

memory = card.read_memory()
memory = card.read_memory(0, 512)

# Display memory content
for offset in range(0, len(memory), 16):
Expand All @@ -60,6 +60,13 @@ def main() -> int:
)
print(f"({offset:03x}): {chunk.hex(' ')} {ascii_values}")

ndef_result = card.get_ndef(memory)
if ndef_result is not None:
start, mem = ndef_result
print(f"NDEF found, it starts at {start}, len={len(mem)}")
print(f"Content:\n{mem.hex(' ')}")
print(f"Next TLV type: {memory[start + len(mem)]:02x}")

return 0

except Exception as e:
Expand Down
10 changes: 10 additions & 0 deletions examples/iso_15693-read-memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ def main() -> int:
card = session.connect_iso15693(uids[0])

try:
memory = b""
for offset in range(0, 512, 16):
chunk = card.read_memory(offset, 16)
memory += chunk
ascii_values = "".join(
chr(byte) if 32 <= byte <= 126 else "."
for byte in chunk
Expand All @@ -70,6 +72,14 @@ def main() -> int:
except TimeoutError:
# Done
pass
ndef_result = card.get_ndef(memory)
if ndef_result is not None:
start, mem = ndef_result
print(
f"NDEF found, it starts at {start}, len={len(mem)}"
)
print(f"Content:\n{mem.hex(' ')}")
print(f"Next TLV type: {memory[start + len(mem)]:02x}")
else:
print("\nNo tags found")

Expand Down
14 changes: 14 additions & 0 deletions src/pn5180_tagomatic/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,17 @@ def write_memory(self, offset: int, data: bytes) -> None:
TimeoutError: If card does not respond.
MemoryWriteError: Other memory write failures.
"""

def get_ndef(self, memory: bytes) -> tuple[int, bytes] | None:
"""Find the NDEF memory.

If found, the start index in the input memory and
its bytes are returned.

Args:
memory: The card's memory, starting from 0

Returns:
(start, ndef_bytes),
or None if it wasn't found.
"""
76 changes: 76 additions & 0 deletions src/pn5180_tagomatic/iso14443a.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,79 @@ def authenticate_for_page(
if len(key_b) != 6:
raise ValueError("key_b must be exactly 6 bytes")
self._keys_b[page_num] = bytes(key_b)

def decode_cc(self, cc: bytes) -> tuple[int, int, int, bool] | None:
"""Decode the CC memory block (block 0)

Args:
cc(bytes): The memory from block 0.

Returns:
(major_version, minor_version, memory size, is readonly)
or None if CC isn't valid.

Raises:
PN5180Error: If communication with the card fails.
ValueError: If cc is less than 4 bytes.
"""
if len(cc) < 4:
raise ValueError("cc should be at least 4 bytes")

if cc[0] != 0xE1:
return None

major = cc[1] >> 4
minor = cc[1] & 0xF

mlen = (cc[2]) * 4

is_readonly = bool((cc[3] & 0xF0) == 0xF0)

return (major, minor, mlen, is_readonly)

def get_ndef(self, memory: bytes) -> tuple[int, bytes] | None:
"""Find the NDEF memory.

Args:
memory: The card's memory, starting from offset 0

Returns:
(start, ndef_bytes),
or None if NDEF couldn't be found.
"""

cc = self.decode_cc(memory[12:16])
if cc is None:
return None

major, _minor, mlen, _ = cc

if major > 1:
return None

if mlen > len(memory):
return None

pos = 16

def read_val(memory: bytes, pos: int) -> tuple[int, int]:
if memory[pos] < 255:
return memory[pos], pos + 1
else:
return (memory[pos + 1] << 8) | memory[pos + 2], pos + 3

while pos < mlen:
typ, pos = read_val(memory, pos)
if typ == 0:
continue
if typ == 0xFE:
# End of TLV
return None
field_len, pos = read_val(memory, pos)
if typ == 0x03:
if pos + field_len > mlen:
return None
return (pos, memory[pos : pos + field_len])
pos += field_len

return None
99 changes: 92 additions & 7 deletions src/pn5180_tagomatic/iso15693.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,91 @@ def memory_block_size(self) -> int:
self._ensure_sys_info_loaded()
return self._block_size

@property
def memory_number_of_blocks(self) -> int:
"""Gets the number of blocks the card contains"""
self._ensure_sys_info_loaded()
return self._num_blocks

def decode_cc(self, cc: bytes) -> tuple[int, int, int, bool] | None:
"""Decode the CC memory block (block 0)

Args:
cc(bytes): The memory from block 0.

Returns:
(major_version, minor_version, memory size, is readonly)
or None if CC isn't valid.

Raises:
PN5180Error: If communication with the card fails.
ValueError: If cc is less than 4 bytes.
"""
if len(cc) < 4:
raise ValueError("cc should be at least 4 bytes")

if cc[0] != 0xE1:
return None

major = cc[1] >> 4
minor = cc[1] & 0xF

mlen = (cc[2] + 1) * 8

is_readonly = bool(cc[3] & 1)

return (major, minor, mlen, is_readonly)

def get_ndef(self, memory: bytes) -> tuple[int, bytes] | None:
"""Find the NDEF memory.

If found, the start index in the input memory and
its bytes are returned.

Args:
memory: The card's memory, starting from 0

Returns:
(start, ndef_bytes),
or None if it wasn't found.
"""

cc = self.decode_cc(memory)
if cc is None:
return None

major, _minor, mlen, _ = cc

if major > 4:
return None

if mlen > len(memory):
return None

pos = 4

def read_val(memory: bytes, pos: int) -> tuple[int, int]:
if memory[pos] < 255:
return memory[pos], pos + 1
else:
return (memory[pos + 1] << 8) | memory[pos + 2], pos + 3

while pos < mlen:
typ, pos = read_val(memory, pos)
if typ == 0:
continue
if typ == 0xFE:
# End of TLV
return None
field_len, pos = read_val(memory, pos)
if typ == 0x03:
if pos + field_len > mlen:
return None
return (pos, memory[pos : pos + field_len])
pos += field_len

return None

def read_memory(self, offset: int = 0, length: int | None = None) -> bytes:
"""Read memory from card.

Expand Down Expand Up @@ -145,24 +230,24 @@ def get_system_information(self) -> dict[str, int]:
"Error getting system information", system_info[1]
)
if len(system_info) < 1:
system_info += b"\0"
raise PN5180Error("Error getting system information, no answer", 0)

pos = 9
pos = 10
result = {}
if system_info[0] & 1:
if system_info[1] & 1:
result["dsfid"] = system_info[pos]
pos += 1
if system_info[0] & 2:
if system_info[1] & 2:
result["afi"] = system_info[pos]
pos += 1
if system_info[0] & 4:
if system_info[1] & 4:
result["num_blocks"] = system_info[pos] + 1
pos += 1
result["block_size"] = (system_info[pos] & 31) + 1
pos += 1
if system_info[0] & 8:
pos += 1
if system_info[1] & 8:
result["ic_reference"] = system_info[pos]
pos += 1

return result

Expand Down
6 changes: 2 additions & 4 deletions src/pn5180_tagomatic/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def get_all_iso14443a_uids(
(0, b"", 0, [], True),
]
while len(discovery_stack) > 0:
(cl, mask, coll_bit, uid, restart) = discovery_stack.pop()
cl, mask, coll_bit, uid, restart = discovery_stack.pop()

if restart:
self._reader.turn_off_crc()
Expand Down Expand Up @@ -246,9 +246,7 @@ def get_all_iso14443a_uids(
self._reader.set_rx_crc_and_first_bit(False, 0)
self._reader.turn_off_tx_crc()
cmd = self._get_cmd_for_level(cl)
(nvb, final_bits) = self._get_nvb_and_final_bits(
len(mask), coll_bit
)
nvb, final_bits = self._get_nvb_and_final_bits(len(mask), coll_bit)

try:
self._reader.set_rx_crc_and_first_bit(False, final_bits)
Expand Down
Loading