mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import platform
|
||||
import sys
|
||||
@@ -7,7 +8,11 @@ import chameleon_com
|
||||
import chameleon_cmd
|
||||
import colorama
|
||||
import chameleon_cli_unit
|
||||
import chameleon_utils
|
||||
import os
|
||||
import prompt_toolkit
|
||||
from prompt_toolkit.formatted_text import ANSI
|
||||
|
||||
|
||||
ULTRA = r"""
|
||||
╦ ╦╦ ╔╦╗╦═╗╔═╗
|
||||
@@ -32,112 +37,45 @@ BANNER = f"""
|
||||
"""
|
||||
|
||||
|
||||
def new_uint(unit_clz, help_msg):
|
||||
"""
|
||||
new a uint dict object
|
||||
:param unit_clz: unit implement class
|
||||
:param help_msg: unit usage
|
||||
:return: a dict...
|
||||
"""
|
||||
return {'unit': unit_clz, 'help': help_msg}
|
||||
|
||||
|
||||
class ChameleonCLI:
|
||||
"""
|
||||
CLI for chameleon
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.cmd_maps = {
|
||||
'hw': {
|
||||
'connect': new_uint(chameleon_cli_unit.HWConnect, "Connect to chameleon by serial port"),
|
||||
'chipid': {
|
||||
'get': new_uint(chameleon_cli_unit.HWChipIdGet, "Get device chipset ID"),
|
||||
'help': "Device chipset ID get"
|
||||
},
|
||||
'address': {
|
||||
'get': new_uint(chameleon_cli_unit.HWAddressGet, "Get device address (used with Bluetooth)"),
|
||||
'help': "Device address get"
|
||||
},
|
||||
'mode': {
|
||||
'set': new_uint(chameleon_cli_unit.HWModeSet, "Change device mode to tag reader or tag emulator"),
|
||||
'get': new_uint(chameleon_cli_unit.HWModeGet, "Get current device mode"),
|
||||
'help': "Device mode get/set"
|
||||
},
|
||||
'slot': {
|
||||
'list': new_uint(chameleon_cli_unit.HWSlotList, "Get information about slots"),
|
||||
'change': new_uint(chameleon_cli_unit.HWSlotSet, "Set emulation tag slot activated."),
|
||||
'type': new_uint(chameleon_cli_unit.HWSlotTagType, "Set emulation tag type"),
|
||||
'init': new_uint(chameleon_cli_unit.HWSlotDataDefault, "Set emulation tag data to default"),
|
||||
'enable': new_uint(chameleon_cli_unit.HWSlotEnableSet, "Set emulation tag slot enable or disable"),
|
||||
'nick': {
|
||||
'set': new_uint(chameleon_cli_unit.HWSlotNickSet, "Set tag nick name for slot"),
|
||||
'get': new_uint(chameleon_cli_unit.HWSlotNickGet, "Get tag nick name for slot"),
|
||||
'help': "Get/Set tag nick name for slot",
|
||||
},
|
||||
'update': new_uint(chameleon_cli_unit.HWSlotUpdate, "Update config & data to device flash"),
|
||||
'openall': new_uint(chameleon_cli_unit.HWSlotOpenAll, "Open all slot and set to default data"),
|
||||
'help': "Emulation tag slot.",
|
||||
},
|
||||
'version': new_uint(chameleon_cli_unit.HWVersion, "Get current device firmware version"),
|
||||
'dfu': new_uint(chameleon_cli_unit.HWDFU, "Restart application to bootloader mode(Not yet implement "
|
||||
"dfu)."),
|
||||
'settings': {
|
||||
'animation': {
|
||||
'get': new_uint(chameleon_cli_unit.HWSettingsAnimationGet, "Get current animation mode value"),
|
||||
'set': new_uint(chameleon_cli_unit.HWSettingsAnimationSet, "Change chameleon animation mode"),
|
||||
'help': 'Manage wake-up and sleep animation mode'
|
||||
},
|
||||
'store': new_uint(chameleon_cli_unit.HWSettingsStore, "Store current settings to flash"),
|
||||
'reset': new_uint(chameleon_cli_unit.HWSettingsReset, "Reset settings to default values"),
|
||||
'help': "Chameleon settings management"
|
||||
},
|
||||
'factory_reset': new_uint(chameleon_cli_unit.HWFactoryReset, "Wipe all data and return to factory "
|
||||
"settings"),
|
||||
'help': "hardware controller",
|
||||
},
|
||||
'hf': {
|
||||
'14a': {
|
||||
'scan': new_uint(chameleon_cli_unit.HF14AScan, "Scan 14a tag, and print basic information"),
|
||||
'info': new_uint(chameleon_cli_unit.HF14AInfo, "Scan 14a tag, and print detail information"),
|
||||
'help': "ISO14443-a tag read/write/info...",
|
||||
},
|
||||
'mf': {
|
||||
'nested': new_uint(chameleon_cli_unit.HFMFNested, "Mifare Classic nested recover key"),
|
||||
'darkside': new_uint(chameleon_cli_unit.HFMFDarkside, "Mifare Classic darkside recover key"),
|
||||
'rdbl': new_uint(chameleon_cli_unit.HFMFRDBL, "Mifare Classic read one block"),
|
||||
'wrbl': new_uint(chameleon_cli_unit.HFMFWRBL, "Mifare Classic write one block"),
|
||||
'detection': {
|
||||
'enable': new_uint(chameleon_cli_unit.HFMFDetectionEnable, "Detection enable"),
|
||||
'count': new_uint(chameleon_cli_unit.HFMFDetectionLogCount, "Detection log count"),
|
||||
'decrypt': new_uint(chameleon_cli_unit.HFMFDetectionDecrypt, "Download log and decrypt keys"),
|
||||
'help': "Mifare Classic detection log"
|
||||
},
|
||||
'settings': new_uint(chameleon_cli_unit.HFMFSettings, "Settings of Mifare Classic emulator"),
|
||||
'sim': new_uint(chameleon_cli_unit.HFMFSim, "Simulate a Mifare Classic card"),
|
||||
'eload': new_uint(chameleon_cli_unit.HFMFELoad, "Load data to emulator memory"),
|
||||
'eread': new_uint(chameleon_cli_unit.HFMFERead, "Read data from emulator memory"),
|
||||
'help': "Mifare Classic mini/1/2/4, attack/read/write"
|
||||
},
|
||||
'help': "high frequency tag/reader",
|
||||
},
|
||||
'lf': {
|
||||
'em': {
|
||||
'read': new_uint(chameleon_cli_unit.LFEMRead, "Scan em410x tag and print id"),
|
||||
'write': new_uint(chameleon_cli_unit.LFEMWriteT55xx, "Write em410x id to t55xx"),
|
||||
'sim': {
|
||||
'set': new_uint(chameleon_cli_unit.LFEMSimSet, "Set simulated em410x card id"),
|
||||
'get': new_uint(chameleon_cli_unit.LFEMSimGet, "Get simulated em410x card id"),
|
||||
'help': "Manage EM410x emulation data for selected slot"
|
||||
},
|
||||
'help': "EM410x read/write/emulator",
|
||||
},
|
||||
'help': "low frequency tag/reader",
|
||||
}
|
||||
}
|
||||
self.completer = chameleon_utils.CustomNestedCompleter.from_nested_dict(chameleon_cli_unit.root_commands)
|
||||
self.session = prompt_toolkit.PromptSession(completer=self.completer)
|
||||
|
||||
# new a device communication instance(only communication)
|
||||
self.device_com = chameleon_com.ChameleonCom()
|
||||
|
||||
def get_cmd_node(self, node: chameleon_utils.CLITree, cmdline: list[str]) -> tuple[chameleon_utils.CLITree, list[str]]:
|
||||
"""
|
||||
Recursively traverse the command line tree to get to the matching node
|
||||
|
||||
:return: last matching CLITree node, remaining tokens
|
||||
"""
|
||||
# No more subcommands to parse, return node
|
||||
if cmdline == []:
|
||||
return node, []
|
||||
|
||||
for child in node.children:
|
||||
if cmdline[0] == child.name:
|
||||
return self.get_cmd_node(child, cmdline[1:])
|
||||
|
||||
# No matching child node
|
||||
return node, cmdline[:]
|
||||
|
||||
def get_prompt(self):
|
||||
"""
|
||||
Retrieve the cli prompt
|
||||
|
||||
:return: current cmd prompt
|
||||
"""
|
||||
device_string = f"{colorama.Fore.GREEN}USB" if self.device_com.isOpen(
|
||||
) else f"{colorama.Fore.RED}Offline"
|
||||
status = f"[{device_string}{colorama.Style.RESET_ALL}] chameleon --> "
|
||||
return status
|
||||
|
||||
@staticmethod
|
||||
def print_banner():
|
||||
@@ -147,23 +85,6 @@ class ChameleonCLI:
|
||||
"""
|
||||
print(colorama.Fore.YELLOW + BANNER)
|
||||
|
||||
def parse_cli_cmd(self, cmd_str):
|
||||
"""
|
||||
parse cmd from str
|
||||
:param cmd_str:
|
||||
:return:
|
||||
"""
|
||||
cmds = cmd_str.split(" ")
|
||||
cmd_maps: dict or types.FunctionType = self.cmd_maps
|
||||
cmd_end_position = 0
|
||||
for cmd in cmds:
|
||||
if cmd in cmd_maps: # CMD found in map, we can continue find next
|
||||
cmd_maps = cmd_maps[cmd]
|
||||
cmd_end_position += len(cmd) + 1
|
||||
else: # CMD not found
|
||||
break
|
||||
return cmd_maps, (cmd_str[:cmd_end_position - 1], cmd_str[cmd_end_position:])
|
||||
|
||||
def startCLI(self):
|
||||
"""
|
||||
start listen input.
|
||||
@@ -173,75 +94,73 @@ class ChameleonCLI:
|
||||
closing = False
|
||||
while True:
|
||||
# wait user input
|
||||
status = f"{colorama.Fore.GREEN}USB" if self.device_com.isOpen() else f"{colorama.Fore.RED}Offline"
|
||||
cmd_str = ""
|
||||
try:
|
||||
cmd_str = input(f"[{status}{colorama.Style.RESET_ALL}] chameleon --> ").strip()
|
||||
cmd_str = self.session.prompt(ANSI(self.get_prompt())).strip()
|
||||
except EOFError:
|
||||
print("")
|
||||
closing = True
|
||||
|
||||
if closing or cmd_str == "exit" or cmd_str == "quit" or cmd_str.startswith('q', 0) or cmd_str.startswith(
|
||||
'e', 0):
|
||||
except KeyboardInterrupt:
|
||||
closing = True
|
||||
|
||||
if closing or cmd_str in ["exit", "quit", "q", "e"]:
|
||||
print("Bye, thank you. ^.^ ")
|
||||
self.device_com.close()
|
||||
sys.exit(996)
|
||||
|
||||
# clear screen
|
||||
if cmd_str == "clear":
|
||||
if platform.system() == 'Windows':
|
||||
os.system("cls")
|
||||
elif platform.system() == 'Linux':
|
||||
os.system("clear")
|
||||
else:
|
||||
print("No screen clear implement")
|
||||
elif cmd_str == "clear":
|
||||
os.system('clear' if os.name == 'posix' else 'cls')
|
||||
continue
|
||||
elif cmd_str == "":
|
||||
continue
|
||||
|
||||
# parse cmd
|
||||
cmd_map, args_str = self.parse_cli_cmd(cmd_str)
|
||||
is_exec_map = 'unit' in cmd_map
|
||||
if is_exec_map:
|
||||
# new a unit instance
|
||||
unit_clz = cmd_map['unit']
|
||||
if callable(unit_clz):
|
||||
unit: chameleon_cli_unit.BaseCLIUnit = unit_clz()
|
||||
else:
|
||||
raise TypeError("CMD unit is not a 'BaseCLIUnit'")
|
||||
# set variables of required
|
||||
unit.device_com = self.device_com
|
||||
# parse args
|
||||
args_parse_result = unit.args_parser()
|
||||
if args_parse_result is not None:
|
||||
args: argparse.ArgumentParser = args_parse_result
|
||||
args.prog = args_str[0]
|
||||
try:
|
||||
args_parse_result = args.parse_args(args_str[1].split())
|
||||
except chameleon_cli_unit.ArgsParserError as e:
|
||||
args.print_usage()
|
||||
print(str(e).strip(), end="\n\n")
|
||||
continue
|
||||
except chameleon_cli_unit.ParserExitIntercept:
|
||||
# don't exit process.
|
||||
continue
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# before process cmd, we need to do something...
|
||||
if not unit.before_exec(args_parse_result):
|
||||
continue
|
||||
# start process cmd
|
||||
unit.on_exec(args_parse_result)
|
||||
except (chameleon_cmd.NegativeResponseError, chameleon_cli_unit.ArgsParserError) as e:
|
||||
print(f"{colorama.Fore.RED}{str(e)}{colorama.Style.RESET_ALL}")
|
||||
except Exception:
|
||||
print(f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}")
|
||||
elif isinstance(cmd_map, dict):
|
||||
argv = cmd_str.split()
|
||||
root_cmd = argv[0]
|
||||
if root_cmd not in chameleon_cli_unit.root_commands:
|
||||
# No matching command group
|
||||
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
|
||||
for map_key in cmd_map:
|
||||
map_item = cmd_map[map_key]
|
||||
if 'help' in map_item:
|
||||
cmd_title = f"{colorama.Fore.GREEN}{map_key}{colorama.Style.RESET_ALL}"
|
||||
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {map_item['help']} ]"
|
||||
print(help_line)
|
||||
for cmd_name, cmd_node in chameleon_cli_unit.root_commands.items():
|
||||
cmd_title = f"{colorama.Fore.GREEN}{cmd_name}{colorama.Style.RESET_ALL}"
|
||||
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {cmd_node.helptext} ]"
|
||||
print(help_line)
|
||||
continue
|
||||
|
||||
tree_node, arg_list = self.get_cmd_node(chameleon_cli_unit.root_commands[root_cmd], argv[1:])
|
||||
|
||||
if not tree_node.cls:
|
||||
# Found tree node is a group without an implementation, print children
|
||||
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
|
||||
for child in tree_node.children:
|
||||
cmd_title = f"{colorama.Fore.GREEN}{child.name}{colorama.Style.RESET_ALL}"
|
||||
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {child.helptext} ]"
|
||||
print(help_line)
|
||||
continue
|
||||
|
||||
unit: chameleon_cli_unit.BaseCLIUnit = tree_node.cls()
|
||||
unit.device_com = self.device_com
|
||||
args_parse_result = unit.args_parser()
|
||||
|
||||
if args_parse_result is not None:
|
||||
args: argparse.ArgumentParser = args_parse_result
|
||||
args.prog = tree_node.fullname
|
||||
try:
|
||||
args_parse_result = args.parse_args(arg_list)
|
||||
except chameleon_utils.ArgsParserError as e:
|
||||
args.print_usage()
|
||||
print(str(e).strip(), end="\n\n")
|
||||
continue
|
||||
except chameleon_utils.ParserExitIntercept:
|
||||
# don't exit process.
|
||||
continue
|
||||
try:
|
||||
# before process cmd, we need to do something...
|
||||
if not unit.before_exec(args_parse_result):
|
||||
continue
|
||||
|
||||
# start process cmd
|
||||
unit.on_exec(args_parse_result)
|
||||
except (chameleon_utils.UnexpectedResponseError, chameleon_utils.ArgsParserError) as e:
|
||||
print(f"{colorama.Fore.RED}{str(e)}{colorama.Style.RESET_ALL}")
|
||||
except Exception:
|
||||
print(f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ import struct
|
||||
|
||||
import chameleon_com
|
||||
import chameleon_status
|
||||
from chameleon_utils import UnexpectedResponseError, expect_response
|
||||
|
||||
DATA_CMD_GET_APP_VERSION = 1000
|
||||
DATA_CMD_CHANGE_MODE = 1001
|
||||
@@ -188,7 +189,7 @@ class MifareClassicWriteMode(enum.IntEnum):
|
||||
return "None"
|
||||
|
||||
|
||||
class BaseChameleonCMD:
|
||||
class ChameleonCMD:
|
||||
"""
|
||||
Chameleon cmd function
|
||||
"""
|
||||
@@ -240,6 +241,7 @@ class BaseChameleonCMD:
|
||||
"""
|
||||
return self.device.send_cmd_sync(DATA_CMD_CHANGE_MODE, 0x00, 0x0001 if reader_mode else 0x0000)
|
||||
|
||||
@expect_response(chameleon_status.Device.HF_TAG_OK)
|
||||
def scan_tag_14a(self):
|
||||
"""
|
||||
扫描场内的14a标签
|
||||
@@ -268,6 +270,7 @@ class BaseChameleonCMD:
|
||||
"""
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_DETECT, 0x00, None, timeout=20)
|
||||
|
||||
@expect_response(chameleon_status.Device.HF_TAG_OK)
|
||||
def detect_nt_distance(self, block_known, type_known, key_known):
|
||||
"""
|
||||
检测卡片的随机数距离
|
||||
@@ -279,6 +282,7 @@ class BaseChameleonCMD:
|
||||
data.extend(key_known)
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_NT_DIST_DETECT, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.HF_TAG_OK)
|
||||
def acquire_nested(self, block_known, type_known, key_known, block_target, type_target):
|
||||
"""
|
||||
采集Nested解密需要的关键NT参数
|
||||
@@ -292,6 +296,7 @@ class BaseChameleonCMD:
|
||||
data.append(block_target)
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.HF_TAG_OK)
|
||||
def acquire_darkside(self, block_target, type_target, first_recover: int or bool, sync_max):
|
||||
"""
|
||||
采集Darkside解密需要的关键参数
|
||||
@@ -310,6 +315,10 @@ class BaseChameleonCMD:
|
||||
data.append(sync_max)
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, 0x00, data, timeout=sync_max + 5)
|
||||
|
||||
@expect_response([
|
||||
chameleon_status.Device.HF_TAG_OK,
|
||||
chameleon_status.Device.MF_ERRAUTH,
|
||||
])
|
||||
def auth_mf1_key(self, block, type_value, key):
|
||||
"""
|
||||
验证mf1秘钥,只验证单个扇区的指定类型的秘钥
|
||||
@@ -324,6 +333,7 @@ class BaseChameleonCMD:
|
||||
data.extend(key)
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.HF_TAG_OK)
|
||||
def read_mf1_block(self, block, type_value, key):
|
||||
"""
|
||||
读取mf1单块
|
||||
@@ -338,6 +348,7 @@ class BaseChameleonCMD:
|
||||
data.extend(key)
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.HF_TAG_OK)
|
||||
def write_mf1_block(self, block, type_value, key, block_data):
|
||||
"""
|
||||
写入mf1单块
|
||||
@@ -354,6 +365,7 @@ class BaseChameleonCMD:
|
||||
data.extend(block_data)
|
||||
return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.LF_TAG_OK)
|
||||
def read_em_410x(self):
|
||||
"""
|
||||
读取EM410X的卡号
|
||||
@@ -361,6 +373,7 @@ class BaseChameleonCMD:
|
||||
"""
|
||||
return self.device.send_cmd_sync(DATA_CMD_SCAN_EM410X_TAG, 0x00)
|
||||
|
||||
@expect_response(chameleon_status.Device.LF_TAG_OK)
|
||||
def write_em_410x_to_t55xx(self, id_bytes: bytearray):
|
||||
"""
|
||||
写入EM410X卡号到T55XX中
|
||||
@@ -392,6 +405,7 @@ class BaseChameleonCMD:
|
||||
"""
|
||||
return self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT, 0x00)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_slot_activated(self, slot_index: SlotNumber):
|
||||
"""
|
||||
Set the card slot currently active for use
|
||||
@@ -403,6 +417,7 @@ class BaseChameleonCMD:
|
||||
data.append(SlotNumber.to_fw(slot_index))
|
||||
return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ACTIVATED, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType):
|
||||
"""
|
||||
设置当前卡槽的模拟卡的标签类型
|
||||
@@ -417,6 +432,7 @@ class BaseChameleonCMD:
|
||||
data.append(tag_type)
|
||||
return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType):
|
||||
"""
|
||||
设置指定卡槽的模拟卡的数据为缺省数据
|
||||
@@ -431,6 +447,7 @@ class BaseChameleonCMD:
|
||||
data.append(tag_type)
|
||||
return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_slot_enable(self, slot_index: SlotNumber, enable: bool):
|
||||
"""
|
||||
设置指定的卡槽是否使能
|
||||
@@ -444,6 +461,7 @@ class BaseChameleonCMD:
|
||||
data.append(0x01 if enable else 0x00)
|
||||
return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, 0X00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_em410x_sim_id(self, id_bytes: bytearray):
|
||||
"""
|
||||
设置EM410x模拟的卡号
|
||||
@@ -460,6 +478,7 @@ class BaseChameleonCMD:
|
||||
"""
|
||||
return self.device.send_cmd_sync(DATA_CMD_GET_EM410X_EMU_ID, 0x00)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_mf1_detection_enable(self, enable: bool):
|
||||
"""
|
||||
设置是否使能当前卡槽的侦测
|
||||
@@ -477,6 +496,7 @@ class BaseChameleonCMD:
|
||||
"""
|
||||
return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def get_mf1_detection_log(self, index: int):
|
||||
"""
|
||||
从指定的index位置开始获取侦测日志
|
||||
@@ -487,6 +507,7 @@ class BaseChameleonCMD:
|
||||
data.extend(index.to_bytes(4, "big", signed=False))
|
||||
return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_RESULT, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_mf1_block_data(self, block_start: int, block_data: bytearray):
|
||||
"""
|
||||
设置MF1的模拟卡的块数据
|
||||
@@ -506,6 +527,7 @@ class BaseChameleonCMD:
|
||||
data = struct.pack('<BH', block_start, block_count)
|
||||
return self.device.send_cmd_sync(DATA_CMD_READ_MF1_EMU_BLOCK_DATA, 0x00, data)
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray):
|
||||
"""
|
||||
设置MF1的模拟卡的防冲撞资源信息
|
||||
@@ -519,7 +541,8 @@ class BaseChameleonCMD:
|
||||
data.extend(atqa)
|
||||
data.extend(uid)
|
||||
return self.device.send_cmd_sync(DATA_CMD_SET_MF1_ANTI_COLLISION_RES, 0X00, data)
|
||||
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes):
|
||||
"""
|
||||
设置MF1的模拟卡的防冲撞资源信息
|
||||
@@ -533,7 +556,8 @@ class BaseChameleonCMD:
|
||||
data.extend([SlotNumber.to_fw(slot), sense_type])
|
||||
data.extend(name)
|
||||
return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, 0x00, data)
|
||||
|
||||
|
||||
@expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType):
|
||||
"""
|
||||
设置MF1的模拟卡的防冲撞资源信息
|
||||
@@ -633,142 +657,11 @@ class BaseChameleonCMD:
|
||||
return self.device.send_cmd_sync(DATA_CMD_WIPE_FDS, 0x00)
|
||||
|
||||
|
||||
class NegativeResponseError(Exception):
|
||||
"""
|
||||
Not positive response
|
||||
"""
|
||||
|
||||
|
||||
class PositiveChameleonCMD(BaseChameleonCMD):
|
||||
"""
|
||||
子类重写基础指令交互实现类,针对每个指令进行单独封装结果处理
|
||||
如果结果是成功状态,那么就返回对应的数据,否则直接抛出异常
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def check_status(status_ret, status_except):
|
||||
"""
|
||||
检查状态码,如果在接受为成功的
|
||||
:param status_ret: 执行指令之后返回的状态码
|
||||
:param status_except: 可以认为是执行成功的状态码
|
||||
:return:
|
||||
"""
|
||||
if isinstance(status_except, int):
|
||||
status_except = [status_except]
|
||||
if status_ret not in status_except:
|
||||
if status_ret in chameleon_status.Device and status_ret in chameleon_status.message:
|
||||
raise NegativeResponseError(chameleon_status.message[status_ret])
|
||||
else:
|
||||
raise NegativeResponseError(f"Not positive response and unknown status {status_ret}")
|
||||
return
|
||||
|
||||
def scan_tag_14a(self):
|
||||
ret = super(PositiveChameleonCMD, self).scan_tag_14a()
|
||||
self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def detect_nt_distance(self, block_known, type_known, key_known):
|
||||
ret = super(PositiveChameleonCMD, self).detect_nt_distance(block_known, type_known, key_known)
|
||||
self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def acquire_nested(self, block_known, type_known, key_known, block_target, type_target):
|
||||
ret = super(PositiveChameleonCMD, self).acquire_nested(block_known, type_known, key_known, block_target,
|
||||
type_target)
|
||||
self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def acquire_darkside(self, block_target, type_target, first_recover: int or bool, sync_max):
|
||||
ret = super(PositiveChameleonCMD, self).acquire_darkside(block_target, type_target, first_recover, sync_max)
|
||||
self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def auth_mf1_key(self, block, type_value, key):
|
||||
ret = super(PositiveChameleonCMD, self).auth_mf1_key(block, type_value, key)
|
||||
self.check_status(ret.status, [chameleon_status.Device.HF_TAG_OK, chameleon_status.Device.MF_ERRAUTH])
|
||||
return ret
|
||||
|
||||
def read_mf1_block(self, block, type_value, key):
|
||||
ret = super(PositiveChameleonCMD, self).read_mf1_block(block, type_value, key)
|
||||
self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def write_mf1_block(self, block, type_value, key, block_data):
|
||||
ret = super(PositiveChameleonCMD, self).write_mf1_block(block, type_value, key, block_data)
|
||||
self.check_status(ret.status, chameleon_status.Device.HF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def read_em_410x(self):
|
||||
ret = super(PositiveChameleonCMD, self).read_em_410x()
|
||||
self.check_status(ret.status, chameleon_status.Device.LF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def write_em_410x_to_t55xx(self, id_bytes: bytearray):
|
||||
ret = super(PositiveChameleonCMD, self).write_em_410x_to_t55xx(id_bytes)
|
||||
self.check_status(ret.status, chameleon_status.Device.LF_TAG_OK)
|
||||
return ret
|
||||
|
||||
def set_slot_activated(self, slot_index):
|
||||
ret = super(PositiveChameleonCMD, self).set_slot_activated(slot_index)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType):
|
||||
ret = super(PositiveChameleonCMD, self).set_slot_tag_type(slot_index, tag_type)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType):
|
||||
ret = super(PositiveChameleonCMD, self).set_slot_data_default(slot_index, tag_type)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_slot_enable(self, slot_index: SlotNumber, enable: bool):
|
||||
ret = super(PositiveChameleonCMD, self).set_slot_enable(slot_index, enable)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_em410x_sim_id(self, id_bytes: bytearray):
|
||||
ret = super(PositiveChameleonCMD, self).set_em410x_sim_id(id_bytes)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_mf1_detection_enable(self, enable: bool):
|
||||
ret = super(PositiveChameleonCMD, self).set_mf1_detection_enable(enable)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def get_mf1_detection_log(self, index: int):
|
||||
ret = super(PositiveChameleonCMD, self).get_mf1_detection_log(index)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_mf1_block_data(self, block_start: int, data: bytearray):
|
||||
ret = super(PositiveChameleonCMD, self).set_mf1_block_data(block_start, data)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray):
|
||||
ret = super(PositiveChameleonCMD, self).set_mf1_anti_collision_res(sak, atqa, uid)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes):
|
||||
ret = super(PositiveChameleonCMD, self).set_slot_tag_nick_name(slot, sense_type, name)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType):
|
||||
ret = super(PositiveChameleonCMD, self).get_slot_tag_nick_name(slot, sense_type)
|
||||
self.check_status(ret.status, chameleon_status.Device.STATUS_DEVICE_SUCCESS)
|
||||
return ret
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# connect to chameleon
|
||||
dev = chameleon_com.ChameleonCom()
|
||||
dev.open("com19")
|
||||
cml = BaseChameleonCMD(dev)
|
||||
cml = ChameleonCMD(dev)
|
||||
ver = cml.get_firmware_version()
|
||||
print(f"Firmware number of application: {ver}")
|
||||
chip = cml.get_device_chip_id()
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
import argparse
|
||||
from functools import wraps
|
||||
from typing import Iterable, Union
|
||||
from prompt_toolkit.completion import Completer, NestedCompleter, WordCompleter
|
||||
from prompt_toolkit.completion.base import CompleteEvent, Completion
|
||||
from prompt_toolkit.document import Document
|
||||
|
||||
import chameleon_status
|
||||
|
||||
|
||||
class ArgsParserError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ParserExitIntercept(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnexpectedResponseError(Exception):
|
||||
"""
|
||||
Unexpected response exception
|
||||
"""
|
||||
|
||||
|
||||
class ArgumentParserNoExit(argparse.ArgumentParser):
|
||||
"""
|
||||
If arg ArgumentParser parse error, we can't exit process,
|
||||
we must raise exception to stop parse
|
||||
"""
|
||||
|
||||
def __init__(self, **args):
|
||||
super().__init__(*args)
|
||||
self.add_help = False
|
||||
self.description = "Please enter correct parameters"
|
||||
|
||||
def exit(self, status: int = ..., message: str or None = ...):
|
||||
if message:
|
||||
raise ParserExitIntercept(message)
|
||||
|
||||
def error(self, message: str):
|
||||
args = {'prog': self.prog, 'message': message}
|
||||
raise ArgsParserError('%(prog)s: error: %(message)s\n' % args)
|
||||
|
||||
|
||||
def expect_response(accepted_responses: Union[int, list[int]]):
|
||||
"""
|
||||
Decorator for wrapping a Chameleon CMD function to check its response
|
||||
for expected return codes and throwing an exception otherwise
|
||||
"""
|
||||
if isinstance(accepted_responses, int):
|
||||
accepted_responses = [accepted_responses]
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def error_throwing_func(*args, **kwargs):
|
||||
ret = func(*args, **kwargs)
|
||||
|
||||
if ret.status not in accepted_responses:
|
||||
if ret.status in chameleon_status.Device and ret.status in chameleon_status.message:
|
||||
raise UnexpectedResponseError(chameleon_status.message[ret.status])
|
||||
else:
|
||||
raise UnexpectedResponseError(f"Unexpected response and unknown status {ret.status}")
|
||||
|
||||
return ret
|
||||
|
||||
return error_throwing_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CLITree:
|
||||
"""
|
||||
Class holding a
|
||||
|
||||
:param name: Name of the command (e.g. "set")
|
||||
:param helptext: Hint displayed for the command
|
||||
:param fullname: Full name of the command that includes previous commands (e.g. "hw mode set")
|
||||
:param cls: A BaseCLIUnit instance handling the command
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, helptext=None, fullname=None, children=None, cls=None) -> None:
|
||||
self.name: str = name
|
||||
self.helptext: str = helptext
|
||||
self.fullname: str = fullname if fullname else name
|
||||
self.children: list[CLITree] = children if children else list()
|
||||
self.cls = cls
|
||||
|
||||
def subgroup(self, name, helptext=None):
|
||||
"""
|
||||
Create a child command group
|
||||
|
||||
:param name: Name of the command group
|
||||
:param helptext: Hint displayed for the group
|
||||
"""
|
||||
child = CLITree(
|
||||
name=name, fullname=f'{self.fullname} {name}', helptext=helptext)
|
||||
self.children.append(child)
|
||||
return child
|
||||
|
||||
def command(self, name, helptext=None):
|
||||
"""
|
||||
Create a child command
|
||||
|
||||
:param name: Name of the command
|
||||
:param helptext: Hint displayed for the command
|
||||
"""
|
||||
def decorator(cls):
|
||||
self.children.append(
|
||||
CLITree(name=name, fullname=f'{self.fullname} {name}', helptext=helptext, cls=cls))
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
|
||||
class CustomNestedCompleter(NestedCompleter):
|
||||
"""
|
||||
Copy of the NestedCompleter class that accepts a CLITree object and
|
||||
supports meta_dict for descriptions
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, options, ignore_case: bool = True, meta_dict: dict = {}
|
||||
) -> None:
|
||||
self.options = options
|
||||
self.ignore_case = ignore_case
|
||||
self.meta_dict = meta_dict
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"CustomNestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})"
|
||||
|
||||
@classmethod
|
||||
def from_nested_dict(cls, data):
|
||||
options = {}
|
||||
meta_dict = {}
|
||||
for key, value in data.items():
|
||||
if isinstance(value, Completer):
|
||||
options[key] = value
|
||||
elif isinstance(value, dict):
|
||||
options[key] = cls.from_nested_dict(value)
|
||||
elif isinstance(value, set):
|
||||
options[key] = cls.from_nested_dict({item: None for item in value})
|
||||
elif isinstance(value, CLITree):
|
||||
if value.cls:
|
||||
# CLITree is a standalone command
|
||||
options[key] = ArgparseCompleter(value.cls().args_parser())
|
||||
else:
|
||||
# CLITree is a command group
|
||||
options[key] = cls.from_clitree(value)
|
||||
meta_dict[key] = value.helptext
|
||||
else:
|
||||
assert value is None
|
||||
options[key] = None
|
||||
|
||||
return cls(options, meta_dict=meta_dict)
|
||||
|
||||
@classmethod
|
||||
def from_clitree(cls, node):
|
||||
options = {}
|
||||
meta_dict = {}
|
||||
|
||||
for child_node in node.children:
|
||||
if child_node.cls and child_node.cls().args_parser():
|
||||
# CLITree is a standalone command with arguments
|
||||
options[child_node.name] = ArgparseCompleter(child_node.cls().args_parser())
|
||||
else:
|
||||
# CLITree is a command group
|
||||
options[child_node.name] = cls.from_clitree(child_node)
|
||||
meta_dict[child_node.name] = child_node.helptext
|
||||
|
||||
return cls(options, meta_dict=meta_dict)
|
||||
|
||||
def get_completions(self, document, complete_event):
|
||||
# Split document.
|
||||
text = document.text_before_cursor.lstrip()
|
||||
stripped_len = len(document.text_before_cursor) - len(text)
|
||||
|
||||
# If there is a space, check for the first term, and use a
|
||||
# subcompleter.
|
||||
if " " in text:
|
||||
first_term = text.split()[0]
|
||||
completer = self.options.get(first_term)
|
||||
|
||||
# If we have a sub completer, use this for the completions.
|
||||
if completer is not None:
|
||||
remaining_text = text[len(first_term) :].lstrip()
|
||||
move_cursor = len(text) - len(remaining_text) + stripped_len
|
||||
|
||||
new_document = Document(
|
||||
remaining_text,
|
||||
cursor_position=document.cursor_position - move_cursor,
|
||||
)
|
||||
|
||||
yield from completer.get_completions(new_document, complete_event)
|
||||
|
||||
# No space in the input: behave exactly like `WordCompleter`.
|
||||
else:
|
||||
completer = WordCompleter(
|
||||
list(self.options.keys()), ignore_case=self.ignore_case, meta_dict=self.meta_dict
|
||||
)
|
||||
yield from completer.get_completions(document, complete_event)
|
||||
|
||||
|
||||
class ArgparseCompleter(Completer):
|
||||
"""
|
||||
Completer instance for autocompletion of ArgumentParser arguments
|
||||
|
||||
:param parser: ArgumentParser instance
|
||||
"""
|
||||
|
||||
def __init__(self, parser) -> None:
|
||||
self.parser: ArgumentParserNoExit = parser
|
||||
|
||||
def check_tokens(self, parsed, unparsed):
|
||||
suggestions = {}
|
||||
def check_arg(tokens):
|
||||
return tokens and tokens[0].startswith('-')
|
||||
|
||||
if not parsed and not unparsed:
|
||||
# No tokens detected, just show all flags
|
||||
for action in self.parser._actions:
|
||||
for opt in action.option_strings:
|
||||
suggestions[opt] = action.help
|
||||
return [], [], suggestions
|
||||
|
||||
token = unparsed.pop(0)
|
||||
|
||||
for action in self.parser._actions:
|
||||
if any(opt == token for opt in action.option_strings):
|
||||
# Argument fully matches the token
|
||||
parsed.append(token)
|
||||
|
||||
if action.choices:
|
||||
# Autocomplete with choices
|
||||
if unparsed:
|
||||
# Autocomplete values
|
||||
value = unparsed.pop(0)
|
||||
for choice in action.choices:
|
||||
if str(choice).startswith(value):
|
||||
suggestions[str(choice)] = None
|
||||
|
||||
parsed.append(value)
|
||||
|
||||
if check_arg(unparsed):
|
||||
parsed, unparsed, suggestions = self.check_tokens(parsed, unparsed)
|
||||
|
||||
else:
|
||||
# Show all possible values
|
||||
for choice in action.choices:
|
||||
suggestions[str(choice)] = None
|
||||
|
||||
break
|
||||
else:
|
||||
# No choices, process further arguments
|
||||
if check_arg(unparsed):
|
||||
parsed, unparsed, suggestions = self.check_tokens(parsed, unparsed)
|
||||
break
|
||||
elif any(opt.startswith(token) for opt in action.option_strings):
|
||||
for opt in action.option_strings:
|
||||
if opt.startswith(token):
|
||||
suggestions[opt] = action.help
|
||||
|
||||
if suggestions:
|
||||
unparsed.insert(0, token)
|
||||
|
||||
return parsed, unparsed, suggestions
|
||||
|
||||
def get_completions(self, document, complete_event):
|
||||
text = document.text_before_cursor
|
||||
word_before_cursor = document.get_word_before_cursor()
|
||||
|
||||
_, _, suggestions = self.check_tokens(list(), text.split())
|
||||
|
||||
for key, suggestion in suggestions.items():
|
||||
yield Completion(key, -len(word_before_cursor), display=key, display_meta=suggestion)
|
||||
@@ -1,2 +1,3 @@
|
||||
pyserial==3.5
|
||||
colorama==0.4.6
|
||||
prompt-toolkit==3.0.39
|
||||
|
||||
Reference in New Issue
Block a user