Skip to content

Commit

Permalink
Merge pull request #168 from RfidResearchGroup/rework_cli
Browse files Browse the repository at this point in the history
Rework cli
  • Loading branch information
doegox authored Oct 10, 2023
2 parents c9e4507 + 61cc4f5 commit ab7592e
Show file tree
Hide file tree
Showing 7 changed files with 1,164 additions and 1,005 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...

## [unreleased][unreleased]
- Added colors to CLI help (@doegox)
- Changed massively CLI, cf https://github.com/RfidResearchGroup/ChameleonUltra/issues/164#issue-1930580576 (@doegox)
- Changed CLI help: lists display and now all commands support `-h` (@doegox)
- Added button action to show battery level (@doegox)
- Added GUI Page docs (@GameTec-live)
Expand Down
12 changes: 6 additions & 6 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ Notes:
* Response: 4+N*8 bytes: `uid[4]` followed by N tuples of `nt[4]|nt_enc[4]`. All values as U32.
* CLI: cf `hf mf nested` on static nonce tag
### 2004: MF1_DARKSIDE_ACQUIRE
* Command: 4 bytes: `type_target|block_target|first_recover|sync_max`
* Command: 4 bytes: `type_target|block_target|first_recover|sync_max`. Type=0x60 for key A, 0x61 for key B.
* Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum,
else 33 bytes `darkside_status|uid[4]|nt1[4]|par[8]|ks1[8]|nr[4]|ar[4]`
* `darkside_status`
Expand All @@ -253,29 +253,29 @@ Notes:
* `ar[4]` U32
* CLI: cf `hf mf darkside`
### 2005: MF1_DETECT_NT_DIST
* Command: 8 bytes: `type_known|block_known|key_known[6]`. Key as 6 bytes.
* Command: 8 bytes: `type_known|block_known|key_known[6]`. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.
* Response: 8 bytes: `uid[4]|dist[4]`
* `uid[4]` U32 (format expected by `nested` tool)
* `dist[4]` U32
* CLI: cf `hf mf nested`
### 2006: MF1_NESTED_ACQUIRE
* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target`. Key as 6 bytes.
* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target`. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.
* Response: N*9 bytes: N tuples of `nt[4]|nt_enc[4]|par`
* `nt[4]` U32
* `nt_enc[4]` U32
* `par`
* CLI: cf `hf mf nested`
### 2007: MF1_AUTH_ONE_KEY_BLOCK
* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes.
* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.
* Response: no data
* Status will be `HF_TAG_OK` if auth succeeded, else `MF_ERR_AUTH`
* CLI: cf `hf mf nested`
### 2008: MF1_READ_ONE_BLOCK
* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes.
* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.
* Response: 16 bytes: `block_data[16]`
* CLI: cf `hf mf rdbl`
### 2009: MF1_WRITE_ONE_BLOCK
* Command: 24 bytes: `type|block|key[6]|block_data[16]`. Key as 6 bytes.
* Command: 24 bytes: `type|block|key[6]|block_data[16]`. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.
* Response: no data
* CLI: cf `hf mf wrbl`
### 2010: HF14A_RAW
Expand Down
2 changes: 1 addition & 1 deletion firmware/Makefile.defs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ APP_FW_VER_MAJOR := $(word 1,$(subst ., ,$(APP_FW_SEMVER)))
APP_FW_VER_MINOR := $(word 2,$(subst ., ,$(APP_FW_SEMVER)))

# Enable NRF_LOG on SWO pin as UART TX
NRF_LOG_UART_ON_SWO_ENABLED := 0
NRF_LOG_UART_ON_SWO_ENABLED := 1

# Enable SDK validation checks
SDK_VALIDATION := 0
113 changes: 30 additions & 83 deletions software/script/chameleon_cli_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
import colorama
import chameleon_cli_unit
import chameleon_utils
import os
import pathlib
import prompt_toolkit
from datetime import datetime
from prompt_toolkit.formatted_text import ANSI
from prompt_toolkit.history import FileHistory

# Colorama shorthands
CR = colorama.Fore.RED
CG = colorama.Fore.GREEN
CB = colorama.Fore.BLUE
CC = colorama.Fore.CYAN
CY = colorama.Fore.YELLOW
CM = colorama.Fore.MAGENTA
C0 = colorama.Style.RESET_ALL

ULTRA = r"""
Expand All @@ -43,43 +43,13 @@
"""


def dump_help(cmd_node, depth=0, dump_cmd_groups=False, dump_description=False):
if cmd_node.cls:
cmd_title = f"{CG}{cmd_node.fullname}{C0}"
if dump_description:
print(f" {cmd_title}".ljust(37) + f"{cmd_node.help_text}")
else:
print(f" {cmd_title}".ljust(37), end="")
p = cmd_node.cls().args_parser()
assert p is not None
p.prog = ""
usage = p.format_usage().removeprefix("usage: ").rstrip()
if usage != "[-h]":
usage = usage.removeprefix("[-h] ")
if dump_description:
print(f"{CG}{C0}".ljust(37), end="")
print(f"{CY}{usage}{C0}")
else:
print("")
else:
if dump_cmd_groups:
cmd_title = f"{CY}{cmd_node.fullname}{C0}"
if dump_description:
print(f" {cmd_title}".ljust(37) + f"{{ {cmd_node.help_text}... }}")
else:
print(f" {cmd_title}")
for child in cmd_node.children:
dump_help(child, depth + 1, dump_cmd_groups, dump_description)


class ChameleonCLI:
"""
CLI for chameleon
"""

def __init__(self):
self.completer = chameleon_utils.CustomNestedCompleter.from_nested_dict(
chameleon_cli_unit.root_commands)
self.completer = chameleon_utils.CustomNestedCompleter.from_clitree(chameleon_cli_unit.root)
self.session = prompt_toolkit.PromptSession(completer=self.completer,
history=FileHistory(pathlib.Path.home() / ".chameleon_history"))

Expand Down Expand Up @@ -132,7 +102,6 @@ def startCLI(self):
raise Exception("This script requires at least Python 3.9")

self.print_banner()
closing = False
cmd_strs = []
while True:
if cmd_strs:
Expand All @@ -145,64 +114,34 @@ def startCLI(self):
cmd_strs = cmd_str.replace(
"\r\n", "\n").replace("\r", "\n").split("\n")
cmd_str = cmd_strs.pop(0)
if cmd_str == "":
continue
except EOFError:
closing = True
cmd_str = 'exit'
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)
elif cmd_str == "clear":
os.system('clear' if os.name == 'posix' else 'cls')
continue
elif cmd_str == "dumphelp":
for _, cmd_node in chameleon_cli_unit.root_commands.items():
dump_help(cmd_node)
continue
elif cmd_str == "":
continue
cmd_str = 'exit'

# look for alternate exit
if cmd_str in ["quit", "q", "e"]:
cmd_str = 'exit'

# look for alternate comments
if cmd_str[0] in ";#%":
cmd_str = 'rem ' + cmd_str[1:].lstrip()

# parse cmd
argv = cmd_str.split()
root_cmd = argv[0]
# look for comments
if root_cmd == "rem" or root_cmd[0] in ";#%":
# precision: second
# iso_timestamp = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
# precision: nanosecond (note that the comment will take some time too, ~75ns, check your system)
iso_timestamp = datetime.utcnow().isoformat() + 'Z'
if root_cmd[0] in ";#%":
comment = ' '.join([root_cmd[1:]]+argv[1:]).strip()
else:
comment = ' '.join(argv[1:]).strip()
print(f"{iso_timestamp} remark: {comment}")
continue
if root_cmd not in chameleon_cli_unit.root_commands:
# No matching command group
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
for cmd_name, cmd_node in chameleon_cli_unit.root_commands.items():
print(f" - {CG}{cmd_name}{C0}".ljust(37) + f"{{ {cmd_node.help_text}... }}")
print(f" - {CG}clear{C0}".ljust(37) + "Clear screen")
print(f" - {CG}exit{C0}".ljust(37) + "Exit program")
print(f" - {CG}rem ...{C0}".ljust(37) + "Display a comment with a timestamp")
continue

tree_node, arg_list = self.get_cmd_node(
chameleon_cli_unit.root_commands[root_cmd], argv[1:])

tree_node, arg_list = self.get_cmd_node(chameleon_cli_unit.root, argv)
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"{CG}{child.name}{C0}"
if not child.cls:
help_line = (f" - {cmd_title}".ljust(37)
) + f"{{ {child.help_text}... }}"
help_line = (f" - {cmd_title}".ljust(37)) + f"{{ {child.help_text}... }}"
else:
help_line = (f" - {cmd_title}".ljust(37)
) + f"{child.help_text}"
help_line = (f" - {cmd_title}".ljust(37)) + f"{child.help_text}"
print(help_line)
continue

Expand All @@ -216,8 +155,8 @@ def startCLI(self):
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")
args.print_help()
print(f'{CY}'+str(e).strip()+f'{C0}', end="\n\n")
continue
except chameleon_utils.ParserExitIntercept:
# don't exit process.
Expand All @@ -227,8 +166,16 @@ def startCLI(self):
if not unit.before_exec(args_parse_result):
continue

# start process cmd
unit.on_exec(args_parse_result)
# start process cmd, delay error to call after_exec firstly
error = None
try:
unit.on_exec(args_parse_result)
except Exception as e:
error = e
unit.after_exec(args_parse_result)
if error is not None:
raise error

except (chameleon_utils.UnexpectedResponseError, chameleon_utils.ArgsParserError) as e:
print(f"{CR}{str(e)}{C0}")
except Exception:
Expand Down
Loading

0 comments on commit ab7592e

Please sign in to comment.