mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
PAC/Stanley CLI: replace --id with --cn/--raw (PM3 parity)
Split the single --id argument into --cn (8 ASCII chars) and --raw (32 hex char T55XX bitstream, directly compatible with PM3 raw output). Add Python-side PAC bitstream encoder/decoder for raw format support. Output now shows CN and Raw labels matching PM3's format. Add NRF_LOG module registration to pac.c for debug logging, consistent with other protocol implementations. Reassign PAC command IDs (3014/3015) to avoid collision with ioProx (3010/3011) after rebase onto upstream/main.
This commit is contained in:
@@ -93,8 +93,8 @@
|
||||
#define DATA_CMD_EM410X_ELECTRA_WRITE_TO_T55XX (3006)
|
||||
#define DATA_CMD_HIDPROX_SCAN (3002)
|
||||
#define DATA_CMD_HIDPROX_WRITE_TO_T55XX (3003)
|
||||
#define DATA_CMD_PAC_SCAN (3010)
|
||||
#define DATA_CMD_PAC_WRITE_TO_T55XX (3011)
|
||||
#define DATA_CMD_PAC_SCAN (3014)
|
||||
#define DATA_CMD_PAC_WRITE_TO_T55XX (3015)
|
||||
#define DATA_CMD_VIKING_SCAN (3004)
|
||||
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
|
||||
#define DATA_CMD_ADC_GENERIC_READ (3009)
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
#include "t55xx.h"
|
||||
#include "tag_base_type.h"
|
||||
|
||||
#define NRF_LOG_MODULE_NAME pac_protocol
|
||||
#include "nrf_log.h"
|
||||
#include "nrf_log_ctrl.h"
|
||||
#include "nrf_log_default_backends.h"
|
||||
NRF_LOG_MODULE_REGISTER();
|
||||
|
||||
#define PAC_DATA_SIZE 8 // 8-byte ASCII card ID
|
||||
|
||||
// NRZ at RF/32: 32 carrier cycles per bit.
|
||||
|
||||
@@ -5661,12 +5661,12 @@ class LFIOProxRead(LFIOProxReadArgsUnit, ReaderRequiredUnit):
|
||||
return self.add_card_arg(parser, required=False)
|
||||
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
ver, fc, cn, raw8, *futureuse = self.cmd.ioprox_scan()
|
||||
print(f"ioProx XSF format")
|
||||
ver, fc, cn, raw8, *futureuse = self.cmd.ioprox_scan()
|
||||
print(f"ioProx XSF format")
|
||||
print(f" Version: {color_string((CG, ver))}")
|
||||
print(f" Facility: {color_string((CG, f'{fc} [0x{fc:02X}]'))}")
|
||||
print(f" ID: {color_string((CY, cn))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
|
||||
@lf_ioprox.command("write")
|
||||
class LFIOProxWriteT55xx(LFIOProxIdArgsUnit, ReaderRequiredUnit):
|
||||
@@ -5686,9 +5686,9 @@ class LFIOProxWriteT55xx(LFIOProxIdArgsUnit, ReaderRequiredUnit):
|
||||
raw8 = self.parse_raw8(args.raw8)
|
||||
ver, fc, cn, raw8, *futureuse = self.cmd.ioprox_decode_raw(raw8)
|
||||
else:
|
||||
res = self.cmd.ioprox_compose_id(args.ver, args.fc, args.cn)
|
||||
res = self.cmd.ioprox_compose_id(args.ver, args.fc, args.cn)
|
||||
raw8 = res[3]
|
||||
|
||||
|
||||
payload16 = struct.pack(
|
||||
">BBH8s4x",
|
||||
ver & 0xFF,
|
||||
@@ -5696,14 +5696,14 @@ class LFIOProxWriteT55xx(LFIOProxIdArgsUnit, ReaderRequiredUnit):
|
||||
cn & 0xFFFF,
|
||||
raw8
|
||||
)
|
||||
|
||||
|
||||
result = self.cmd.ioprox_write_to_t55xx(payload16)
|
||||
|
||||
print(f"ioProx XSF format")
|
||||
print(f"ioProx XSF format")
|
||||
print(f" Version: {color_string((CG, ver))}")
|
||||
print(f" Facility: {color_string((CG, f'{fc} [0x{fc:02X}]'))}")
|
||||
print(f" ID: {color_string((CY, cn))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
print("Write done.")
|
||||
|
||||
@lf_ioprox.command("econfig")
|
||||
@@ -5736,9 +5736,9 @@ class LFIOProxEconfig(SlotIndexArgsAndGoUnit, LFIOProxIdArgsUnit):
|
||||
raw8 = self.parse_raw8(args.raw8)
|
||||
ver, fc, cn, raw8, *futureuse = self.cmd.ioprox_decode_raw(raw8)
|
||||
else:
|
||||
res = self.cmd.ioprox_compose_id(args.ver, args.fc, args.cn)
|
||||
res = self.cmd.ioprox_compose_id(args.ver, args.fc, args.cn)
|
||||
raw8 = res[3]
|
||||
|
||||
|
||||
payload16 = struct.pack(
|
||||
">BBH8s4x",
|
||||
ver & 0xFF,
|
||||
@@ -5748,21 +5748,102 @@ class LFIOProxEconfig(SlotIndexArgsAndGoUnit, LFIOProxIdArgsUnit):
|
||||
)
|
||||
|
||||
result = self.cmd.ioprox_set_emu_id(payload16)
|
||||
|
||||
print(f"ioProx XSF format")
|
||||
|
||||
print(f"ioProx XSF format")
|
||||
print(f" Version: {color_string((CG, ver))}")
|
||||
print(f" Facility: {color_string((CG, f'{fc} [0x{fc:02X}]'))}")
|
||||
print(f" ID: {color_string((CY, cn))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
|
||||
else:
|
||||
# GET
|
||||
ver, fc, cn, raw8, *futureuse = self.cmd.ioprox_get_emu_id()
|
||||
print(f"ioProx XSF format")
|
||||
print(f"ioProx XSF format")
|
||||
print(f" Version: {color_string((CG, ver))}")
|
||||
print(f" Facility: {color_string((CG, f'{fc} [0x{fc:02X}]'))}")
|
||||
print(f" ID: {color_string((CY, cn))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
|
||||
|
||||
def pac_encode_raw(card_id: bytes) -> bytes:
|
||||
"""Encode 8-byte card ID to 16-byte T55XX bitstream (128 bits).
|
||||
|
||||
Frame: 0xFF sync (8 bits) + 12 × 10-bit UART frames.
|
||||
UART frame: start(0) + 7 data bits LSB-first + odd parity + stop(1).
|
||||
Payload: STX(0x02), '2', '0', card_id[0..7], XOR checksum.
|
||||
"""
|
||||
payload = [0x02, 0x32, 0x30] + list(card_id)
|
||||
xor_check = 0
|
||||
for b in card_id:
|
||||
xor_check ^= b
|
||||
payload.append(xor_check)
|
||||
|
||||
bits = [1] * 8 # sync marker 0xFF
|
||||
for byte_val in payload:
|
||||
bits.append(0) # start bit
|
||||
ones = 0
|
||||
for i in range(7):
|
||||
bit = (byte_val >> i) & 1
|
||||
bits.append(bit)
|
||||
ones += bit
|
||||
bits.append(0 if (ones & 1) else 1) # odd parity
|
||||
bits.append(1) # stop bit
|
||||
|
||||
raw = bytearray(16)
|
||||
for i in range(128):
|
||||
if bits[i]:
|
||||
raw[i >> 3] |= 1 << (7 - (i & 7))
|
||||
return bytes(raw)
|
||||
|
||||
|
||||
def pac_decode_raw(raw: bytes) -> bytes:
|
||||
"""Decode 16-byte T55XX bitstream to 8-byte card ID.
|
||||
|
||||
Validates sync, UART framing, header, and checksum.
|
||||
"""
|
||||
if len(raw) != 16:
|
||||
raise ValueError("Raw data must be exactly 16 bytes (128 bits)")
|
||||
|
||||
def get_bit(pos):
|
||||
return (raw[pos >> 3] >> (7 - (pos & 7))) & 1
|
||||
|
||||
# Sync marker
|
||||
for i in range(8):
|
||||
if not get_bit(i):
|
||||
raise ValueError("Invalid sync marker (expected 0xFF)")
|
||||
|
||||
decoded = []
|
||||
for f in range(12):
|
||||
start = 8 + f * 10
|
||||
if get_bit(start):
|
||||
raise ValueError(f"Invalid start bit in UART frame {f}")
|
||||
byte_val = 0
|
||||
ones = 0
|
||||
for i in range(7):
|
||||
if get_bit(start + 1 + i):
|
||||
byte_val |= 1 << i
|
||||
ones += 1
|
||||
if get_bit(start + 8):
|
||||
ones += 1
|
||||
if (ones & 1) == 0:
|
||||
raise ValueError(f"Parity error in UART frame {f}")
|
||||
if not get_bit(start + 9):
|
||||
raise ValueError(f"Invalid stop bit in UART frame {f}")
|
||||
decoded.append(byte_val)
|
||||
|
||||
if decoded[0] != 0x02:
|
||||
raise ValueError(f"Invalid STX: 0x{decoded[0]:02X}")
|
||||
if decoded[1] != 0x32 or decoded[2] != 0x30:
|
||||
raise ValueError("Invalid header (expected '20')")
|
||||
|
||||
card_id = bytes(decoded[3:11])
|
||||
xor_check = 0
|
||||
for b in card_id:
|
||||
xor_check ^= b
|
||||
if xor_check != decoded[11]:
|
||||
raise ValueError(f"Checksum error: expected 0x{xor_check:02X}, got 0x{decoded[11]:02X}")
|
||||
|
||||
return card_id
|
||||
|
||||
|
||||
@lf_pac.command('read')
|
||||
class LFPacRead(ReaderRequiredUnit):
|
||||
@@ -5774,32 +5855,47 @@ class LFPacRead(ReaderRequiredUnit):
|
||||
def on_exec(self, args: argparse.Namespace):
|
||||
card_id = self.cmd.pac_scan()
|
||||
card_id_ascii = ''.join(chr(b) if 0x20 <= b < 0x7f else '.' for b in card_id)
|
||||
print(f" PAC/Stanley Card ID: {color_string((CG, card_id_ascii))} ({card_id.hex().upper()})")
|
||||
raw = pac_encode_raw(card_id)
|
||||
print(f" PAC/Stanley - CN: {color_string((CG, card_id_ascii))} | Raw: {raw.hex().upper()}")
|
||||
|
||||
|
||||
class LFPacIdArgsUnit(DeviceRequiredUnit):
|
||||
@staticmethod
|
||||
def add_card_arg(parser: ArgumentParserNoExit, required=False):
|
||||
parser.add_argument("--id", type=str, required=required,
|
||||
help="PAC/Stanley card ID: 16 hex chars (e.g. 4141414141414141) or 8 ASCII chars (e.g. AAAAAAAA)",
|
||||
metavar="<hex|ascii>")
|
||||
group = parser.add_mutually_exclusive_group(required=required)
|
||||
group.add_argument("--cn", type=str,
|
||||
help="Card number (8 ASCII characters, e.g. CARD0001)",
|
||||
metavar="<ascii>")
|
||||
group.add_argument("--raw", type=str,
|
||||
help="T55XX bitstream (32 hex chars, PM3 raw format)",
|
||||
metavar="<hex>")
|
||||
return parser
|
||||
|
||||
def before_exec(self, args: argparse.Namespace):
|
||||
if not super().before_exec(args):
|
||||
return False
|
||||
if args.id is not None:
|
||||
if re.match(r"^[a-fA-F0-9]{16}$", args.id):
|
||||
pass # already hex
|
||||
elif len(args.id) == 8:
|
||||
# treat as 8 ASCII characters, convert to hex
|
||||
args.id = args.id.encode('ascii').hex()
|
||||
else:
|
||||
raise ArgsParserError("ID must be 16 hex chars (8 bytes) or 8 ASCII characters")
|
||||
# PAC uses 7-bit UART frames; MSB of each byte is not encoded
|
||||
id_bytes = bytes.fromhex(args.id)
|
||||
if any(b > 0x7F for b in id_bytes):
|
||||
raise ArgsParserError("PAC card IDs are 7-bit ASCII only (each byte must be 0x00-0x7F)")
|
||||
if args.cn is not None:
|
||||
if len(args.cn) != 8:
|
||||
raise ArgsParserError("Card number must be exactly 8 characters")
|
||||
try:
|
||||
args.id = args.cn.encode('ascii').hex()
|
||||
except UnicodeEncodeError:
|
||||
raise ArgsParserError("Card number must be ASCII characters only")
|
||||
elif args.raw is not None:
|
||||
if not re.match(r"^[a-fA-F0-9]{32}$", args.raw):
|
||||
raise ArgsParserError("Raw must be exactly 32 hex characters (128-bit T55XX bitstream)")
|
||||
try:
|
||||
card_id = pac_decode_raw(bytes.fromhex(args.raw))
|
||||
except ValueError as e:
|
||||
raise ArgsParserError(f"Invalid PAC raw data: {e}")
|
||||
args.id = card_id.hex()
|
||||
else:
|
||||
args.id = None
|
||||
return True
|
||||
# PAC uses 7-bit UART frames; MSB of each byte is not encoded
|
||||
id_bytes = bytes.fromhex(args.id)
|
||||
if any(b > 0x7F for b in id_bytes):
|
||||
raise ArgsParserError("PAC card IDs are 7-bit only (each byte must be 0x00-0x7F)")
|
||||
return True
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit:
|
||||
@@ -5819,7 +5915,8 @@ class LFPacWriteT55xx(LFPacIdArgsUnit, ReaderRequiredUnit):
|
||||
id_bytes = bytes.fromhex(args.id)
|
||||
self.cmd.pac_write_to_t55xx(id_bytes)
|
||||
id_ascii = ''.join(chr(b) if 0x20 <= b < 0x7f else '.' for b in id_bytes)
|
||||
print(f" - PAC/Stanley ID write done: {id_ascii} ({args.id.upper()})")
|
||||
raw = pac_encode_raw(id_bytes)
|
||||
print(f" - PAC/Stanley write done - CN: {id_ascii} | Raw: {raw.hex().upper()}")
|
||||
|
||||
@lf_pac.command('econfig')
|
||||
class LFPacEconfig(SlotIndexArgsAndGoUnit, LFPacIdArgsUnit):
|
||||
@@ -5842,8 +5939,9 @@ class LFPacEconfig(SlotIndexArgsAndGoUnit, LFPacIdArgsUnit):
|
||||
else:
|
||||
response = self.cmd.pac_get_emu_id()
|
||||
card_id_ascii = ''.join(chr(b) if 0x20 <= b < 0x7f else '.' for b in response)
|
||||
raw = pac_encode_raw(response)
|
||||
print(' - Get PAC/Stanley tag id success.')
|
||||
print(f'ID: {card_id_ascii} ({response.hex().upper()})')
|
||||
print(f'CN: {card_id_ascii} | Raw: {raw.hex().upper()}')
|
||||
|
||||
@lf_viking.command("read")
|
||||
class LFVikingRead(ReaderRequiredUnit):
|
||||
@@ -6103,7 +6201,9 @@ class HWSlotList(DeviceRequiredUnit):
|
||||
if lf_tag_type == TagSpecificType.PAC:
|
||||
id = self.cmd.pac_get_emu_id()
|
||||
id_ascii = ''.join(chr(b) if 0x20 <= b < 0x7f else '.' for b in id)
|
||||
print(f" {'ID:':40}{color_string((CY, f'{id_ascii} ({id.hex().upper()})'))}")
|
||||
raw = pac_encode_raw(id)
|
||||
print(f" {'CN:':40}{color_string((CY, id_ascii))}")
|
||||
print(f" {'Raw:':40}{color_string((CY, raw.hex().upper()))}")
|
||||
if current != selected:
|
||||
self.cmd.set_active_slot(selected)
|
||||
|
||||
|
||||
@@ -83,8 +83,8 @@ class Command(enum.IntEnum):
|
||||
HIDPROX_WRITE_TO_T55XX = 3003
|
||||
VIKING_SCAN = 3004
|
||||
VIKING_WRITE_TO_T55XX = 3005
|
||||
PAC_SCAN = 3010
|
||||
PAC_WRITE_TO_T55XX = 3011
|
||||
PAC_SCAN = 3014
|
||||
PAC_WRITE_TO_T55XX = 3015
|
||||
ADC_GENERIC_READ = 3009
|
||||
IOPROX_SCAN = 3010
|
||||
IOPROX_WRITE_TO_T55XX = 3011
|
||||
|
||||
Reference in New Issue
Block a user