From 8499535aadcc965ff3e94857c77d8770f820be80 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 10 Sep 2023 14:13:10 +0200 Subject: [PATCH] Clarify protocol. Disruptive changes: see below This huge commit tries to enhance several things related to the fw/cli protocol. Generally, the idea is to be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors. docs/protocol.md got heavily updated Many commands have been renamed for consistency. you are invited to adapt your client for easier maintenance Guidelines, also written in docs/protocol.md "New data payloads: guidelines for developers": - Now protocol data exchanged over USB or BLE are defined in netdata.h as packed structs and values are stored in Network byte order (=Big Endian) - Command-specific payloads are defined in their respective cmd_processor handler in app_cmd.c and chameleon_cmd.py - Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. - If single byte of data to return, still use a 1-byte `data`, not `status`. - Use unambiguous types such as `uint16_t`, not `int` or `enum`. Cast explicitly `int` and `enum` to `uint_t` of proper size - Use Network byte order for 16b and 32b integers - Macros `U16NTOHS`, `U32NTOHL` must be used on reception of a command payload. - Macros `U16HTONS`, `U32HTONL` must be used on creation of a response payload. - In Python, use the modifier `!` with all `struct.pack`/`struct.unpack` - Concentrate payload parsing in the handlers, avoid further parsing in their callers. This is true for the firmware and the client. - In cmd_processor handlers: don't reuse input `length`/`data` parameters for creating the response content - Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python - Use the exact same command and fields names in firmware and in client, use function names matching the command names for their handlers unless there is a very good reason not to do so. This helps grepping around. Names must start with a letter, not a number, because some languages require it (e.g. `14a_scan` not possible in Python) - Respect commands order in `m_data_cmd_map`, `data_cmd.h` and `chameleon_cmd.py` definitions - Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to `data_cmd.h` and `chameleon_cmd.py` with some `FIXME: to be implemented` comment - Validate data before using it, both when receiving command data in the firmware and when receiving response data in the client. - Validate response status in client. Disruptive changes: - GET_DEVICE_CAPABILITIES: list of cmds in data are now really Big Endian Note: the initial attempt to use macros PP_HTONS were actually considering wrongly that the platform was Big Endian (BYTE_ORDER was actually undefined) while it is actually Little Endian. - GET_APP_VERSION: response is now a tuple of bytes: major|minor (previously it was in reversed order as a single uint16_t in Little Endian) - SET_SLOT_TAG_TYPE: tag_type now on 2 bytes, to prepare remapping of its enum - SET_SLOT_DATA_DEFAULT: tag_type now on 2 bytes, to prepare remapping of its enum - GET_SLOT_INFO: tag_type now on 2 bytes, to prepare remapping of its enum - GET_DEVICE_CHIP_ID: now returns its 64b ID following Network byte order (previously, bytes were in the reverse order) - GET_DEVICE_ADDRESS: now returns its 56b address following Network byte order (previously, bytes were in the reverse order). CLI does not reverse the response anymore so it displays the same value as before. - MF1_GET_DETECTION_COUNT: now returns its 32b value following Network byte order (previously Little Endian) - GET_GIT_VERSION response status is now STATUS_DEVICE_SUCCESS - GET_DEVICE_MODEL response status is now STATUS_DEVICE_SUCCESS - MF1_READ_EMU_BLOCK_DATA response status is now STATUS_DEVICE_SUCCESS - GET_DEVICE_CAPABILITIES response status is now STATUS_DEVICE_SUCCESS - HF14A_SCAN: entirely new response format, room for ATS and multiple tags - MF1_DETECT_SUPPORT response status is now HF_TAG_OK and support is indicated as bool in 1 byte of data - MF1_DETECT_PRNG response status is now HF_TAG_OK and prng_type is returned in 1 byte of data with a new enum mf1_prng_type_t == MifareClassicPrngType - MF1_DETECT_DARKSIDE response status is now HF_TAG_OK and darkside_status is returned in 1 byte of data with a new enum mf1_darkside_status_t == MifareClassicDarksideStatus - MF1_DARKSIDE_ACQUIRE response status is now HF_TAG_OK and darkside_status is returned in 1 byte of data. If OK, followed by 24 bytes as previously - MF1_GET_ANTI_COLL_DATA: in case slot does not contain anticoll data, instead of STATUS_PAR_ERR, now it returns STATUS_DEVICE_SUCCESS with empty data - MF1_SET_ANTI_COLL_DATA and MF1_GET_ANTI_COLL_DATA now use the same data format as HF14A_SCAN For clients to detect Ultra/Lite with older firmwares, one can issue the GET_APP_VERSION and urge the user to flash his device if needed. On older firmwares, it will return a status=b'\x00' and data=b'\x00\x01' while up-to-date firmwares will return status=STATUS_DEVICE_SUCCESS and data greater or equal to b'\x01\x00' (v1.0). Other changes: cf CHANGELOG, and probably a few small changes I forgot about.. TODO: - remap `tag_specific_type_t` enum to allow future tags (e.g. LF tags) without reshuffling enum and affecting users stored cards - TEST! --- CHANGELOG.md | 13 +- docs/development.md | 4 + docs/images/Makefile | 5 + docs/images/protocol-packet.png | Bin 25723 -> 8684 bytes docs/images/protocol-packet.tex | 24 + docs/protocol.md | 381 +++++- firmware/application/src/app_cmd.c | 1216 ++++++++--------- firmware/application/src/app_cmd.h | 1 - firmware/application/src/app_main.c | 1 - firmware/application/src/app_status.h | 11 - firmware/application/src/ble_main.c | 6 +- firmware/application/src/data_cmd.h | 67 +- .../application/src/rfid/nfctag/hf/nfc_mf1.c | 11 +- .../application/src/rfid/nfctag/hf/nfc_mf1.h | 20 +- .../src/rfid/reader/hf/mf1_toolbox.c | 117 +- .../src/rfid/reader/hf/mf1_toolbox.h | 39 +- .../application/src/rfid/reader/hf/rc522.h | 4 +- firmware/application/src/settings.c | 4 +- firmware/application/src/settings.h | 4 +- firmware/application/src/utils/dataframe.c | 151 +- firmware/application/src/utils/dataframe.h | 13 +- firmware/application/src/utils/netdata.h | 65 + firmware/common/hw_connect.c | 24 +- firmware/common/lwip_def.h | 142 -- software/script/chameleon_cli_unit.py | 491 ++++--- software/script/chameleon_cmd.py | 711 ++++++---- software/script/chameleon_com.py | 76 +- software/script/chameleon_cstruct.py | 113 -- software/script/chameleon_status.py | 29 +- software/script/chameleon_utils.py | 5 +- 30 files changed, 2097 insertions(+), 1651 deletions(-) create mode 100644 docs/images/Makefile create mode 100644 docs/images/protocol-packet.tex create mode 100644 firmware/application/src/utils/netdata.h delete mode 100644 firmware/common/lwip_def.h delete mode 100644 software/script/chameleon_cstruct.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cedbcb..a6919af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,18 @@ 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 `hf settings blepair` command to get and set ble pairing enable state, and default disable ble pair. (@xianglin1998) + - Changed `hw detection decrypt` show progression and remove duplicate keys (@doegox) + - Changed dynamic cmd_map_init() by static cmd_map initialization (@doegox) + - Changed `hf slot list` to add clarity and colors (@doegox) + - Changed `hf mf sim` and `hf mf info` to support ATS (still to be used in actual emulation) (@doegox) + - Changed `hf mf eload` and `hf mf eread`: uploads/downloads are now 30x faster (@doegox) + - Changed CLI HF14AInfo logic merged inside HF14AScan for more consistent display of the results (@doegox) + - Added guessed type information for NXP tags, and reorganization of HF information part. (@FlUxIuS) + - Changed `hw raw` to detail status message (@doegox) + - Changed CLI to query capabilities on connect, not on every single command if device does not support get_device_capabilities (@doegox) + - Changed CLI to not instanciate ChameleonCMD on every single command (@doegox) + - Changed massively the protocol and its handlers for more consistency and easier maintenance and future dev (@doegox) + - Added `hf settings blepair` command to get and set ble pairing enable state, and default disable ble pair (@xianglin1998) - Added `hf mf info` command to get UID/SAK/ATQA from slot (@Foxushka) - Added `hw raw` to send raw command to Chameleon (@Foxushka) - Added command to fetch all available commands from Chameleon and test if Chameleon supports it (@Foxushka) diff --git a/docs/development.md b/docs/development.md index da352db2..409dbd56 100644 --- a/docs/development.md +++ b/docs/development.md @@ -345,3 +345,7 @@ Limitations: * SWO pin is shared with... SWO so when e.g. reflashing the device, garbage may appear on the monitoring terminal. * SWO pin is also shared with the blue channel of the RGB slot LEDs, so faint blue may appear briefly when logs are sent and LED might not work properly when supposed to be blue. + +# Resources + +* [nRF52840 Objective Product Specification v0.5.1](https://infocenter.nordicsemi.com/pdf/nRF52840_OPS_v0.5.1.pdf) diff --git a/docs/images/Makefile b/docs/images/Makefile new file mode 100644 index 00000000..814efe75 --- /dev/null +++ b/docs/images/Makefile @@ -0,0 +1,5 @@ +all: + pdflatex --shell-escape protocol-packet.tex + +clean: + rm *.aux *.log *.pdf diff --git a/docs/images/protocol-packet.png b/docs/images/protocol-packet.png index 98372b5e33a16b31b5565c41ba1b9d5d8eda832e..2092f14fd5575bfc0bc99b911a7ea49e9313de44 100644 GIT binary patch literal 8684 zcmbt(c|6qH|Nltorck$qAxjxch%tl+bz|(KxmiM4@}{y4veS%9B^ujEWbHPE7G%l3 z+{oxn2!*l6kbN1l3^V-RquZy?{XV{*&mZ5Paprx_dA-g#uk(B@=k-1lc>$xx!!5`S zgTZ(V^mTrR!MIQ`*bdk)oKTNw;4V|>vGdwFlXEawLF`_p!!D>T=BWR>2@Dn_34?{+ zhQZdMuFwe>%ohoRP1(a>>d7z|zh_zn_6!WhvFn1dneNus7R2-YkAs7Qlaq7j&YeFx zVBhPaf`VIA>S-kMm)*O+lN8j{1Vu!6_wVQ4$E~2Iz`2u?y0rxj_l=CyHZY)WQTcx5 z%dD(yo|~&07|8qZA+s{Gd9Jx?pi1A|Tvkab{>2L?fB&#Yk9ZFpP}kA%xpzMQus?t$c|B#ngnVISDC)aXNJ$9>I_Jy+ zdS?f2oTpibB@bs9qb5P%B5Bf0YA1JJ=-w9#VXB2KJ~DEYSBaxrTZ#I04`Ts%Wz@5? z_zb?)tIZ?pg5;!|#cO;7X#W5EUugHTWmP(1L}KA;y%K8~cM|=skCAN~R=U(ywbQI~ zh}r6_(_Tx;M!*kWeEE5*Q(>A%Ap)qXAbNo8H~x>@b{EU4MN`+IHPn z$}S$ILBe+Ql@FMjR^2tKdFydgn?(fmWDq zk%H36{4AjAE^R_OvSF^fzB+wYI+GZVi`}a|E20%VtG# z|3GmUeY#>}Y+}KP$q5o-)4yH=bIg$I*iZ0JBMNs}?VhgF%2SQ2CE4?F*jo;F=6tZh zn>pp!{I3#;#3TjE%zRfmjm5f*@>_XQAMQqbBYJD^rr{tJv$-~~ai(&4Os$5Yi!z)+ zkju!0<<;qv0pYb-0@2u}e?s>n7(Hd_b#H?|Pa)~F-=5Ng3u$O|j@#7Xu!~W25RgQ87 zdtT_yGNKuRxoevH_er=gQp2&zsE=_(eqRBP4}NvI__TXWt`oL`Dvp(9Z+oL-BpjUt zGOP$qWjKx;7pg#ab{UW48K%brXJp~@y+^CQwn(dAPRmhGY27K=D0tKW{l@uOl!k1t z1FB?w$sn65ef9NmxPdR)FWiW7e=fa6xIxJjjY|E!zjXa%|C5s*zwp%

M(e2{T3g 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 diff --git a/docs/images/protocol-packet.tex b/docs/images/protocol-packet.tex new file mode 100644 index 00000000..82449ac0 --- /dev/null +++ b/docs/images/protocol-packet.tex @@ -0,0 +1,24 @@ +\documentclass[border=10pt,png]{standalone} +\usepackage{bytefield} +\usepackage{xcolor} + +\begin{document} + + \definecolor{lightcyan}{rgb}{0.85,1,1} + \definecolor{lightgreen}{rgb}{0.85,1,0.85} + \definecolor{lightred}{rgb}{1,0.85,0.85} + \begin{bytefield}[bitwidth=1.1em]{32} + \bitbox{8}[bgcolor=lightcyan]{SOF} & + \bitbox{8}[bgcolor=lightcyan]{LRC1} & + \bitbox{16}[bgcolor=lightgreen]{CMD} \\ + \bitbox{16}[bgcolor=lightgreen]{STATUS} & + \bitbox{16}[bgcolor=lightgreen]{LEN} \\ + \bitbox{8}[bgcolor=lightgreen]{LRC2} & + \bitbox[tlr]{24}[bgcolor=lightred]{} \\ + \wordbox[lr]{1}[bgcolor=lightred]{DATA} \\ + \wordbox[lr]{1}[bgcolor=lightred]{$\cdots$} \\ + \bitbox[blr]{24}[bgcolor=lightred]{} & + \bitbox{8}[bgcolor=lightred]{LRC3} + \end{bytefield} + +\end{document} diff --git a/docs/protocol.md b/docs/protocol.md index 70f57257..16a55b39 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -2,28 +2,379 @@ **WIP** -## Packets format +## Frame format -The communication with the application is not the easiest but is structured as follows: +The communication between the firmware and the client is made of frames structured as follows: ![](images/protocol-packet.png) -- **SOF**: `1 Byte`, the "Magic Byte" represent the start of a packet, must be `0x11`. -- **LRC1**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `SOF`, must be `0xEF`. -- **CMD**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format, each command have been assigned a unique number (e.g. `factoryReset(1020)`), this is what you are sending to the device. -- **STATUS**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format. If the direction is from APP to hardware, the status is always `0x0000`. If the direction is from hardware to APP, the status is the result of the command. -- **LEN**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format, the length of the data, maximum is `512`. -- **LRC2**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `CMD`, `STATUS` and `LEN`. -- **DATA**: `LEN Bytes`, the data to send or receive, maximum is `512 Bytes`. This could be anything, for example you should sending key type, block number, and the card keys when reading a block. -- **LRC3**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `DATA`. +- **SOF**: `1 byte`, "**S**tart-**O**f-**F**rame byte" represents the start of a packet, and must be equal to `0x11`. +- **LRC1**: `1 byte`, LRC over `SOF` byte, therefore must be equal to `0xEF`. +- **CMD**: `2 bytes`, each command have been assigned a unique number (e.g. `DATA_CMD_SET_SLOT_TAG_NICK` = `1007`). +- **STATUS**: `2 bytes`. + - From client to firmware, the status is always `0x0000`. + - From firmware to client, the status is the result of the command. +- **LEN**: `2 bytes`, length of the `DATA` field, maximum is `512`. +- **LRC2**: `1 byte`, LRC over `CMD|STATUS|LEN` bytes. +- **DATA**: `LEN bytes`, data to be sent or received, maximum is `512 bytes`. This payload depends on the exact command or response to command being used. See [Packet payloads](#packet-payloads) below. +- **LRC3**: `1 byte`, LRC over `DATA` bytes. -The total length of the packet is `LEN + 10` Bytes. For receiving, it is the exact same format. - -Note: LRC2 and LRC3 can be computed equally as covering either the frame from its first byte or from the byte following the previous LRC, because previous LRC nullifies previous bytes LRC computation. +Notes: +* The same frame format is used for commands and for responses. +* All values are **unsigned** values, and if more than one byte, in **network byte order**, aka [Big Endian](https://en.wikipedia.org/wiki/Endianness) byte order. +* The total length of the packet is `LEN + 10` bytes, therefore it is between `10` and `522` bytes. +* The LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) is the 8-bit two's-complement value of the sum of all bytes modulo $2^8$. +* LRC2 and LRC3 can be computed equally as covering either the frame from its first byte or from the byte following the previous LRC, because previous LRC nullifies previous bytes LRC computation. E.g. LRC3(DATA) == LRC3(whole frame) -## Packet payloads +## Data payloads Each command and response have their own payload formats. -TODO: +Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. +See [Guidelines](#new-data-payloads-guidelines-for-developers) for more info. + +* **TODO:** remap `tag_specific_type_t` enum. Maybe dissociate LF & HF types in 2 enums +* **TODO:** num_to_bytes bytes_to_num +* **TODO:** mf1_darkside_acquire /nested acquire deep PACKED struct... +* **FIXME:** mf1_get_emulator_config with bits -> bytes (5) with 4 bools <> mf1_get_detection_log with bitfield (2)... + +Beware, slots in protocol count from 0 to 7 (and from 1 to 8 in the CLI...). + +In the following list, "CLI" refers to one typical CLI command using the described protocol command. But it's not a 1:1 match, there can be other protocol commands used by the CLI command and there can be other CLI commands using the same protocol command... + +### 1000: GET_APP_VERSION +* Command: no data +* Response: 2 bytes: `version_major|version_minor` +* CLI: cf `hw version` +### 1001: CHANGE_DEVICE_MODE +* Command: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* Response: no data +* CLI: cf `hw mode set` +### 1002: GET_DEVICE_MODE +* Command: no data +* Response: data: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* CLI: cf `hw mode get` +### 1003: SET_ACTIVE_SLOT +* Command: 1 byte. `slot_number` between 0 and 7 +* Response: no data +* CLI: cf `hw slot change` +### 1004: SET_SLOT_TAG_TYPE +* Command: 3 bytes. `slot_number|tag_type[2]` with `slot_number` between 0 and 7 and `tag_type` according to `tag_specific_type_t` enum, U16 in Network byte order. +* Response: no data +* CLI: cf `hw slot type` +### 1005: SET_SLOT_DATA_DEFAULT +* Command: 3 bytes. `slot_number|tag_type[2]` with `slot_number` between 0 and 7 and `tag_type` U16 according to `tag_specific_type_t` enum, U16 in Network byte order. +* Response: no data +* CLI: cf `hw slot init` +### 1006: SET_SLOT_ENABLE +* Command: 2 bytes. `slot_number|enable` with `slot_number` between 0 and 7 and `enable` = `0x01` to enable, `0x00` to disable +* Response: no data +* CLI: cf `hw slot enable` +### 1007: SET_SLOT_TAG_NICK +* Command: 2+N bytes. `slot_number|sense_type|name[N]` with `slot_number` between 0 and 7, `sense_type` according to `tag_sense_type_t` enum and `name` a UTF-8 encoded string of max 32 bytes, no null terminator. +* Response: no data +* CLI: cf `hw slot nick set` +### 1008: GET_SLOT_TAG_NICK +* Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. +* Response: a UTF-8 encoded string of max 32 bytes, no null terminator. If no nick name has been recorded in Flash, response status is `STATUS_FLASH_READ_FAIL`. +* CLI: cf `hw slot nick get` +### 1009: SLOT_DATA_CONFIG_SAVE +* Command: no data +* Response: no data +* CLI: cf `hw slot update` +### 1010: ENTER_BOOTLOADER +* Command: no data +* Response: this special command does not return and will interrupt the communication link while rebooting in bootloader mode, needed for DFU. +* CLI: cf `hw dfu` +### 1011: GET_DEVICE_CHIP_ID +* Command: no data +* Response: 8 bytes. nRF `DEVICEID[8]` U64 in Network byte order. +* CLI: cf `hw chipid get` +### 1012: GET_DEVICE_ADDRESS +* Command: no data +* Response: 6 bytes. nRF `DEVICEADDR[6]` U48 in Network byte order. First 2 MSBits forced to `0b11` to match BLE static address. +* CLI: cf `hw address get` +### 1013: SAVE_SETTINGS +* Command: no data +* Response: no data +* CLI: cf `hw settings store` +### 1014: RESET_SETTINGS +* Command: no data +* Response: no data +* CLI: cf `hw settings reset` +### 1015: SET_ANIMATION_MODE +* Command: 1 byte, according to `settings_animation_mode_t` enum. +* Response: no data +* CLI: cf `hw settings animation set` +### 1016: GET_ANIMATION_MODE +* Command: no data +* Response: 1 byte, according to `settings_animation_mode_t` enum. +* CLI: cf `hw settings animation get` +### 1017: GET_GIT_VERSION +* Command: no data +* Response: n bytes, a UTF-8 encoded string, no null terminator. +* CLI: cf `hw version` +### 1018: GET_ACTIVE_SLOT +* Command: no data +* Response: 1 byte +* CLI: cf `hw slot list` +### 1019: GET_SLOT_INFO +* Command: no data +* Response: 32 bytes, 8 tuples `hf_tag_type[2]|lf_tag_type[2]` according to `tag_specific_type_t` enum, for slots from 0 to 7, U16 in Network byte order. +* CLI: cf `hw slot list` +### 1020: WIPE_FDS +* Command: no data +* Response: no data. Status is `STATUS_DEVICE_SUCCESS` or `STATUS_FLASH_WRITE_FAIL`. The device will reboot shortly after this command. +* CLI: cf `hw factory_reset` +### 1023: GET_ENABLED_SLOTS +* Command: no data +* Response: 8 bytes, 8 bool = `0x00` or `0x01`, for slots from 0 to 7 +### 1024: DELETE_SLOT_SENSE_TYPE +* Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. +* Response: no data +* CLI: cf `hw factory_reset` +### 1025: GET_BATTERY_INFO +* Command: no data +* Response: 3 bytes, `voltage[2]|percentage`. Voltage: U16 in Network byte order. +* CLI: cf `hw battery` +### 1026: GET_BUTTON_PRESS_CONFIG +* Command: 1 byte. Char `A` or `B` (`a`/`b` tolerated too) +* Response: 1 byte, `button_function` according to `settings_button_function_t` enum. +* CLI: cf `hw settings btnpress get` +### 1027: SET_BUTTON_PRESS_CONFIG +* Command: 2 bytes. `button|button_function` with `button` char `A` or `B` (`a`/`b` tolerated too) and `button_function` according to `settings_button_function_t` enum. +* Response: no data +* CLI: cf `hw settings btnpress set` +### 1028: GET_LONG_BUTTON_PRESS_CONFIG +* Command: 1 byte. Char `A` or `B` (`a`/`b` tolerated too) +* Response: 1 byte, `button_function` according to `settings_button_function_t` enum. +* CLI: cf `hw settings btnpress get` +### 1029: SET_LONG_BUTTON_PRESS_CONFIG +* Command: 2 bytes. `button|button_function` with `button` char `A` or `B` (`a`/`b` tolerated too) and `button_function` according to `settings_button_function_t` enum. +* Response: no data +* CLI: cf `hw settings btnpress set` +### 1030: SET_BLE_PAIRING_KEY +* Command: 6 bytes. 6 ASCII-encoded digits. +* Response: no data +* CLI: cf `hw settings blekey` +### 1031: GET_BLE_PAIRING_KEY +* Command: no data +* Response: 6 bytes. 6 ASCII-encoded digits. +* CLI: cf `hw settings blekey` +### 1032: DELETE_ALL_BLE_BONDS +* Command: no data +* Response: no data +* CLI: cf `hw ble bonds clear` +### 1033: GET_DEVICE_MODEL +* Command: no data +* Response: 1 byte. `hw_version` aka `NRF_DFU_HW_VERSION` (0=Ultra, 1=Lite) +* CLI: cf `hw version` +### 1034: GET_DEVICE_SETTINGS +* Command: no data +* Response: 14 bytes + * `settings_current_version` = `5` + * `animation_mode`, cf [GET_ANIMATION_MODE](#1016-get_animation_mode) + * `btn_press_A`, cf [GET_BUTTON_PRESS_CONFIG](#1026-get_button_press_config) + * `btn_press_B`, cf [GET_BUTTON_PRESS_CONFIG](#1026-get_button_press_config) + * `btn_long_press_A`, cf [GET_LONG_BUTTON_PRESS_CONFIG](#1028-get_long_button_press_config) + * `btn_long_press_B`, cf [GET_LONG_BUTTON_PRESS_CONFIG](#1028-get_long_button_press_config) + * `ble_pairing_enable`, cf [GET_BLE_PAIRING_ENABLE](#1036-get_ble_pairing_enable) + * `ble_pairing_key[6]`, cf [GET_BLE_PAIRING_KEY](#1031-get_ble_pairing_key) +* CLI: unused +### 1035: GET_DEVICE_CAPABILITIES +* Command: no data +* Response: 2*n bytes, a list of supported commands IDs. +* CLI: used internally on connect +### 1036: GET_BLE_PAIRING_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hw settings blepair` +### 1037: SET_BLE_PAIRING_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hw settings blepair` +### 2000: HF14A_SCAN +* Command: no data +* Response: N bytes: `tag1_data|tag2_data|...` with each tag: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* CLI: cf `hf 14a scan` + +Notes: +* remind that if no tag is present, status will be `HF_TAG_NO` and Response empty. +* at the moment, the firmware supports only one tag, but get your client ready for more! +* `atslen` must not be confused with `ats[0]`==`TL`. So `atslen|ats` = `00` means no ATS while `0100` would be an empty ATS. +### 2001: MF1_DETECT_SUPPORT +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hf 14a info` +### 2002: MF1_DETECT_PRNG +* Command: no data +* Response: 1 byte, according to `mf1_nested_type_t` enum +* CLI: cf `hf 14a info` +### 2003: MF1_DETECT_DARKSIDE +* Command: no data +* Response: 1 byte, according to `mf1_darkside_status_t` enum +* CLI: unused +* **FIXME:** always `CANT_FIX_NT` or watchdog reset on static nonce cards +### 2004: MF1_DARKSIDE_ACQUIRE +* Command: 4 bytes: `type_target|block_target|first_recover|sync_max` +* Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum, + else 25 bytes `darkside_status|uid[4]|nt1[4]|par[4]|ks1[4]|nr[4]|ar[4]` + * `darkside_status` + * `uid[4]` U32 (format expected by `darkside` tool) + * `nt1[4]` U32 + * `par[4]` U32 + * `ks1[4]` U32 + * `nr[4]` U32 + * `ar[4]` U32 +* CLI: cf `hf mf darkside` +* **FIXME:** always `CANT_FIX_NT` or watchdog reset on static nonce cards +### 2005: MF1_DETECT_NT_DIST +* Command: 8 bytes: `type_known|block_known|key_known[6]`. Key as 6 bytes. +* 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. +* 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. +* 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. +* 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. +* Response: no data +* CLI: cf `hf mf wrbl` +### 3000: EM410X_SCAN +* Command: no data +* Response: 5 bytes. `id[5]`. ID as 5 bytes. +* CLI: cf `lf em read` +### 3001: EM410X_WRITE_TO_T55XX +* Command: 9+N*4 bytes: `id[5]|new_key[4]|old_key1[4]|old_key2[4]|...` (N>=1). . ID as 5 bytes. Keys as 4 bytes. +* Response: no data +* CLI: cf `lf em write` +### 4000: MF1_WRITE_EMU_BLOCK_DATA +* Command: 1+N*16 bytes: `block_start|block_data1[16]|block_data2[16]|...` (1<=N<=31) +* Response: no data +* CLI: cf `hf mf eload` +### 4001: HF14A_SET_ANTI_COLL_DATA +* Command: N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* Response: no data +* CLI: cf `hf mf sim` +### 4004: MF1_SET_DETECTION_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf detection enable` +### 4005: MF1_GET_DETECTION_COUNT +* Command: no data +* Response: 4 bytes, `count[4]`, U32 in Network byte order. +* CLI: cf `hf detection count` +### 4006: MF1_GET_DETECTION_LOG +* Command: 4 bytes, `index`, U32 in Network byte order. +* Response: N*18 bytes. 0<=N<=28 + * `block` + * `...|is_nested|is_key_b` 1-byte bitfield, starting from LSB + * `uid[4]` ? + * `nt[4]` ? + * `nr[4]` ? + * `ar[4]` ? +* CLI: cf `hf detection decrypt` +### 4007: MF1_GET_DETECTION_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hw slot list` +### 4008: MF1_READ_EMU_BLOCK_DATA +* Command: 2 bytes: `block_start|block_count` with 1<=`block_count` <=32 +* Response: `block_count`*16 bytes +* CLI: cf `hf mf eread` +### 4009: MF1_GET_EMULATOR_CONFIG +* Command: no data +* Response: 5 bytes + * `detection`, cf [MF1_GET_DETECTION_ENABLE](#4007-mf1_get_detection_enable) + * `gen1a_mode`, cf [MF1_GET_GEN1A_MODE](#4010-mf1_get_gen1a_mode) + * `gen2_mode`, cf [MF1_GET_GEN2_MODE](#4012-mf1_get_gen2_mode) + * `block_anti_coll_mode`, cf [MF1_GET_BLOCK_ANTI_COLL_MODE](#4014-mf1_get_block_anti_coll_mode) + * `write_mode`, cf [MF1_GET_WRITE_MODE](#4016-mf1_get_write_mode) +* CLI: cf `hw slot list` +### 4010: MF1_GET_GEN1A_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4011: MF1_SET_GEN1A_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4012: MF1_GET_GEN2_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4013: MF1_SET_GEN2_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4014: MF1_GET_BLOCK_ANTI_COLL_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4015: MF1_SET_BLOCK_ANTI_COLL_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4016: MF1_GET_WRITE_MODE +* Command: no data +* Response: 1 byte, according to `nfc_tag_mf1_write_mode_t` aka `MifareClassicWriteMode` enum +* CLI: unused +### 4017: MF1_SET_WRITE_MODE +* Command: 1 byte, according to `nfc_tag_mf1_write_mode_t` aka `MifareClassicWriteMode` enum +* Response: no data +* CLI: cf `hf mf settings` +### 4018: HF14A_GET_ANTI_COLL_DATA +* Command: no data +* Response: no data or N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* CLI: cf `hf mf info` +### 5000: EM410X_SET_EMU_ID +* Command: 5 bytes. `id[5]`. ID as 5 bytes. +* Response: no data +* CLI: cf `lf em sim set` +### 5001: EM410X_GET_EMU_ID +* Command: no data +* Response: 5 bytes. `id[5]`. ID as 5 bytes. +* CLI: cf `lf em sim get` + +## New data payloads: guidelines for developers + +If you need to define new payloads for new commands, try to follow these guidelines. + +### Guideline: Verbose and explicit +Be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors +### Guideline: Structs +- Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. Exceptions to `C` struct are when the formats are of variable length (but Python `struct` is still flexible enough to cope with such formats!) +- Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python +### Guideline: Status +If single byte of data to return, still use a 1-byte `data`, not `status`. Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. If the response status is different than those, the response data is empty. Response status are generic and cover things like tag disappearance or tag non-conformities with the ISO standard. If a command needs more specific response status, it is added in the first byte of the data, to avoid cluttering the 1-byte general status enum with command-specific statuses. See e.g. [MF1_DARKSIDE_ACQUIRE](#2004-mf1_darkside_acquire). +### Guideline: unambiguous types +- Use unambiguous types such as `uint16_t`, not `int` or `enum`. Cast explicitly `int` and `enum` to `uint_t` of proper size +- Use Network byte order for 16b and 32b integers + - Macros `U16NTOHS`, `U32NTOHL` must be used on reception of a command payload. + - Macros `U16HTONS`, `U32HTONL` must be used on creation of a response payload. + - In Python, use the modifier `!` with all `struct.pack`/`struct.unpack` +### Guideline: payload parsing in handlers +- Concentrate payload parsing in the handlers, avoid further parsing in their callers. Callers should not care about the protocol. This is true for the firmware and the client. +- In cmd_processor handlers: don't reuse input `length`/`data` parameters for creating the response content +### Guideline: Naming conventions +- Use the exact same command and fields names in firmware and in client, use function names matching the command names for their handlers unless there is a very good reason not to do so. This helps grepping around. Names must start with a letter, not a number, because some languages require it (e.g. `14a_scan` not possible in Python) +- Respect commands order in `m_data_cmd_map`, `data_cmd.h` and `chameleon_cmd.py` definitions +- Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to `data_cmd.h` and `chameleon_cmd.py` with some `FIXME: to be implemented` comment +### Guideline: Validate status and data +- Validate response status in client before parsing data. +- Validate data before using it. diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 9400edbd..e5b46165 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -14,7 +14,7 @@ #include "nrf_pwr_mgmt.h" #include "settings.h" #include "delayed_reset.h" -#include "lwip_def.h" +#include "netdata.h" #define NRF_LOG_MODULE_NAME app_cmd @@ -24,59 +24,66 @@ NRF_LOG_MODULE_REGISTER(); +static void change_slot_auto(uint8_t slot) { + device_mode_t mode = get_device_mode(); + tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); + light_up_by_slot(); + set_slot_light_color(0); +} + -data_frame_tx_t *cmd_processor_get_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint16_t version = FW_VER_NUM; - return data_frame_make(cmd, status, 2, (uint8_t *)&version); +static data_frame_tx_t *cmd_processor_get_app_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint8_t version_major; + uint8_t version_minor; + } PACKED payload; + payload.version_major = APP_FW_VER_MAJOR; + payload.version_minor = APP_FW_VER_MINOR; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - return data_frame_make(cmd, status, strlen(GIT_VERSION), (uint8_t *)GIT_VERSION); +static data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, strlen(GIT_VERSION), (uint8_t *)GIT_VERSION); } -data_frame_tx_t *cmd_processor_get_device(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_device_model(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { #if defined(PROJECT_CHAMELEON_ULTRA) - uint8_t device = 1; + uint8_t resp_data = 1; #else - uint8_t device = 0; + uint8_t resp_data = 0; #endif - return data_frame_make(cmd, status, 1, &device); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } -data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1) { - if (data[0] == 1) { +static data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (data[0] > 1)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + if (data[0] == 1) { #if defined(PROJECT_CHAMELEON_ULTRA) - reader_mode_enter(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); + reader_mode_enter(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); #else - return data_frame_make(cmd, STATUS_NOT_IMPLEMENTED, 0, NULL); + return data_frame_make(cmd, STATUS_NOT_IMPLEMENTED, 0, NULL); #endif - } else { + } else { #if defined(PROJECT_CHAMELEON_ULTRA) - tag_mode_enter(); + tag_mode_enter(); #endif - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); - } - } else { - return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } } -data_frame_tx_t *cmd_processor_get_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - device_mode_t mode = get_device_mode(); - if (mode == DEVICE_MODE_READER) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_get_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t resp_data = (get_device_mode() == DEVICE_MODE_READER); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } -data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // restart to boot #define BOOTLOADER_DFU_GPREGRET_MASK (0xB0) #define BOOTLOADER_DFU_START_BIT_MASK (0x01) @@ -86,39 +93,48 @@ data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, u nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_DFU); // Never into here... while (1) __NOP(); + // For the compiler to be happy... + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint32_t chip_id[2]; - chip_id[0] = NRF_FICR->DEVICEID[0]; - chip_id[1] = NRF_FICR->DEVICEID[1]; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, (uint8_t *)(&chip_id[0])); +static data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint32_t chip_HSW; + uint32_t chip_LSW; + } PACKED payload; + payload.chip_LSW = U32HTONL(NRF_FICR->DEVICEID[0]); + payload.chip_HSW = U32HTONL(NRF_FICR->DEVICEID[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint32_t device_address[2]; +static data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // The FICR value is a just a random number, with no knowledge // of the Bluetooth Specification requirements for random addresses. // So we need to set a Bluetooth LE random address as a static address. // See: https://github.com/zephyrproject-rtos/zephyr/blob/7b6b1328a0cb96fe313a5e2bfc57047471df236e/subsys/bluetooth/controller/hci/nordic/hci_vendor.c#L29 - device_address[0] = NRF_FICR->DEVICEADDR[0]; - device_address[1] = NRF_FICR->DEVICEADDR[1] | 0xC000; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 6, (uint8_t *)(&device_address[0])); + + struct { + uint16_t device_address_HSW; + uint32_t device_address_LSW; + } PACKED payload; + payload.device_address_LSW = U32HTONL(NRF_FICR->DEVICEADDR[0]); + payload.device_address_HSW = U16HTONS(NRF_FICR->DEVICEADDR[1] | 0xC000); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_save_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_save_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { status = settings_save_config(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_reset_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_reset_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { settings_init_config(); status = settings_save_config(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_get_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t settings[7 + BLE_CONNECT_KEY_LEN_MAX] = {}; +static data_frame_tx_t *cmd_processor_get_device_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t settings[7 + BLE_PAIRING_KEY_LEN] = {}; settings[0] = SETTINGS_CURRENT_VERSION; // current version settings[1] = settings_get_animation_config(); // animation mode settings[2] = settings_get_button_press_config('A'); // short A button press mode @@ -126,354 +142,411 @@ data_frame_tx_t *cmd_processor_get_settings(uint16_t cmd, uint16_t status, uint1 settings[4] = settings_get_long_button_press_config('A'); // long A button press mode settings[5] = settings_get_long_button_press_config('B'); // long B button press mode settings[6] = settings_get_ble_pairing_enable(); // is device require pairing - memcpy(settings + 7, settings_get_ble_connect_key(), BLE_CONNECT_KEY_LEN_MAX); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 7 + BLE_CONNECT_KEY_LEN_MAX, settings); + memcpy(settings + 7, settings_get_ble_connect_key(), BLE_PAIRING_KEY_LEN); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 7 + BLE_PAIRING_KEY_LEN, settings); } -data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1) { - status = STATUS_DEVICE_SUCCESS; - settings_set_animation_config(data[0]); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (data[0] > 2)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_animation_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t animation_mode = settings_get_animation_config(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&animation_mode)); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &animation_mode); } -data_frame_tx_t *cmd_processor_get_battery_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t resp[3] = { 0x00 }; - // set voltage - num_to_bytes(batt_lvl_in_milli_volts, 2, resp); - // set percentage - resp[2] = percentage_batt_lvl; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp), resp); +static data_frame_tx_t *cmd_processor_get_battery_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint16_t voltage; + uint8_t percent; + } PACKED payload; + payload.voltage = U16HTONS(batt_lvl_in_milli_volts); + payload.percent = percentage_batt_lvl; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t button_press_config; - if (length == 1 && is_settings_button_type_valid(data[0])) { - button_press_config = settings_get_button_press_config(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); + uint8_t button_press_config = settings_get_button_press_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(button_press_config), &button_press_config); } -data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && is_settings_button_type_valid(data[0])) { - settings_set_button_press_config(data[0], data[1]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_button_press_config(data[0], data[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t button_press_config; - if (length == 1 && is_settings_button_type_valid(data[0])) { - button_press_config = settings_get_long_button_press_config(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); + uint8_t button_press_config = settings_get_long_button_press_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(button_press_config), &button_press_config); } -data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && is_settings_button_type_valid(data[0])) { - settings_set_long_button_press_config(data[0], data[1]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_long_button_press_config(data[0], data[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t is_enable = settings_get_ble_pairing_enable(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&is_enable)); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &is_enable); } -data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == true || data[0] == false)) { - settings_set_ble_pairing_enable(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_ble_pairing_enable(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } #if defined(PROJECT_CHAMELEON_ULTRA) -data_frame_tx_t *cmd_processor_14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_hf14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { picc_14a_tag_t taginfo; status = pcd_14a_reader_scan_auto(&taginfo); - if (status == HF_TAG_OK) { - length = sizeof(picc_14a_tag_t); - data = (uint8_t *)&taginfo; - } else { - length = 0; - data = NULL; + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + uint8_t payload[1 + sizeof(taginfo.uid) + sizeof(taginfo.atqa) + sizeof(taginfo.sak) + 1 + 254]; + uint16_t offset = 0; + payload[offset++] = taginfo.uid_len; + memcpy(&payload[offset], taginfo.uid, taginfo.uid_len); + offset += taginfo.uid_len; + memcpy(&payload[offset], taginfo.atqa, sizeof(taginfo.atqa)); + offset += sizeof(taginfo.atqa); + payload[offset++] = taginfo.sak; + payload[offset++] = 0; // TODO: no ATS support yet + return data_frame_make(cmd, HF_TAG_OK, offset, payload); +} + +static data_frame_tx_t *cmd_processor_mf1_detect_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t support; + status = check_std_mifare_nt_support((bool *)&support); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); + return data_frame_make(cmd, HF_TAG_OK, sizeof(support), &support); } -data_frame_tx_t *cmd_processor_detect_mf1_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_std_mifare_nt_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t type; + status = check_prng_type((mf1_prng_type_t *)&type); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(type), &type); } -data_frame_tx_t *cmd_processor_detect_mf1_nt_level(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_weak_nested_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_detect_darkside(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t darkside_status; + status = check_darkside_support((mf1_darkside_status_t *)&darkside_status); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(darkside_status), &darkside_status); } -data_frame_tx_t *cmd_processor_detect_mf1_darkside(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_darkside_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 4) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + struct { + uint8_t darkside_status; + // DarksideCore_t is PACKED and comprises only bytes so we can use it directly + DarksideCore_t dc; + } PACKED payload; + status = darkside_recover_key(data[1], data[0], data[2], data[3], &payload.dc, (mf1_darkside_status_t *)&payload.darkside_status); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + if (payload.darkside_status != DARKSIDE_OK) { + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload.darkside_status), &payload.darkside_status); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - DarksideCore dc; - if (length == 4) { - status = darkside_recover_key(data[1], data[0], data[2], data[3], &dc); - if (status == HF_TAG_OK) { - length = sizeof(DarksideCore); - data = (uint8_t *)(&dc); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; +static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, data); -} -data_frame_tx_t *cmd_processor_mf1_nt_distance(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedDist nd; - if (length == 8) { - status = nested_distance_detect(data[1], data[0], &data[2], &nd); - if (status == HF_TAG_OK) { - length = sizeof(NestedDist); - data = (uint8_t *)(&nd); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; + struct { + uint8_t uid[4]; + uint32_t distance; + } PACKED payload_resp; + + payload_t *payload = (payload_t *)data; + uint32_t distance; + status = nested_distance_detect(payload->block_known, payload->type_known, payload->key_known, payload_resp.uid, &distance); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + payload_resp.distance = U32HTONL(distance); + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); +} + +static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + NestedCore_t ncs[SETS_NR]; + typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, data); -} -data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedCore ncs[SETS_NR]; - if (length == 10) { - status = nested_recover_key(bytes_to_num(&data[2], 6), data[1], data[0], data[9], data[8], ncs); - if (status == HF_TAG_OK) { - length = sizeof(ncs); - data = (uint8_t *)(&ncs); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; + payload_t *payload = (payload_t *)data; + status = nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, ncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); + // NestedCore_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(ncs), (uint8_t *)(&ncs)); } -data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 8) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - pcd_14a_reader_mf1_unauth(); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + payload_t *payload = (payload_t *)data; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + pcd_14a_reader_mf1_unauth(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + payload_t *payload = (payload_t *)data; uint8_t block[16] = { 0x00 }; - if (length == 8) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_read(data[1], block); - if (status == HF_TAG_OK) { - length = 16; - } else { - length = 0; - } - } else { - length = 0; - } - } else { - length = 0; - status = STATUS_PAR_ERR; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, block); + status = pcd_14a_reader_mf1_read(payload->block, block); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, status, sizeof(block), block); } -data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 24) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_write(data[1], &data[8]); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + uint8_t block_data[16]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + payload_t *payload = (payload_t *)data; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + status = pcd_14a_reader_mf1_write(payload->block, payload->block_data); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t id_buffer[5] = { 0x00 }; status = PcdScanEM410X(id_buffer); - return data_frame_make(cmd, status, sizeof(id_buffer), id_buffer); + if (status != LF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, LF_TAG_OK, sizeof(id_buffer), id_buffer); } -data_frame_tx_t *cmd_processor_write_em410x_2_t57(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length >= 13 && (length - 9) % 4 == 0) { - status = PcdWriteT55XX(data, data + 5, data + 9, (length - 9) / 4); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_em410x_write_to_t55XX(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t id[5]; + uint8_t old_key[4]; + uint8_t new_keys[4]; // we can have more than one... struct just to compute offsets with min 1 key + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if (length < sizeof(payload_t) || (length - offsetof(payload_t, new_keys)) % sizeof(payload->new_keys) != 0) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + status = PcdWriteT55XX(payload->id, payload->old_key, payload->new_keys, (length - offsetof(payload_t, new_keys)) / sizeof(payload->new_keys)); return data_frame_make(cmd, status, 0, NULL); } #endif -static void change_slot_auto(uint8_t slot) { - device_mode_t mode = get_device_mode(); - tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); - light_up_by_slot(); - set_slot_light_color(0); +static data_frame_tx_t *cmd_processor_set_active_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] >= TAG_MAX_SLOT_NUM) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + change_slot_auto(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_activated(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && data[0] < TAG_MAX_SLOT_NUM) { - change_slot_auto(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint16_t tag_type; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + + payload_t *payload = (payload_t *)data; + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot >= TAG_MAX_SLOT_NUM || tag_type == TAG_TYPE_UNKNOWN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + tag_emulation_change_type(payload->num_slot, tag_type); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && data[1] != TAG_TYPE_UNKNOWN) { - uint8_t num_slot = data[0]; - uint8_t tag_type = data[1]; - tag_emulation_change_type(num_slot, (tag_specific_type_t)tag_type); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint8_t sense_type; + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if ((length != sizeof(payload_t)) || + (payload->num_slot >= TAG_MAX_SLOT_NUM) || + (payload->sense_type != TAG_SENSE_HF && payload->sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + + tag_emulation_delete_data(payload->num_slot, payload->sense_type); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = STATUS_PAR_ERR; - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && (data[1] == TAG_SENSE_HF || data[1] == TAG_SENSE_LF)) { - uint8_t slot_num = data[0]; - uint8_t sense_type = data[1]; +static data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint16_t tag_type; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } - tag_emulation_delete_data(slot_num, sense_type); - status = STATUS_DEVICE_SUCCESS; + payload_t *payload = (payload_t *)data; + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot >= TAG_MAX_SLOT_NUM || tag_type == TAG_TYPE_UNKNOWN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + status = tag_emulation_factory_data(payload->num_slot, tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && data[1] != TAG_TYPE_UNKNOWN) { - uint8_t num_slot = data[0]; - uint8_t tag_type = data[1]; - status = tag_emulation_factory_data(num_slot, (tag_specific_type_t)tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t slot_index; + uint8_t enabled; + } PACKED payload_t; + + payload_t *payload = (payload_t *)data; + if (length != sizeof(payload_t) || + payload->slot_index >= TAG_MAX_SLOT_NUM || + payload->enabled > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); -} -data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && (data[1] == 0 || data[1] == 1)) { - uint8_t slot_now = data[0]; - bool enable = data[1]; - tag_emulation_slot_set_enable(slot_now, enable); - if (!enable) { - uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); - NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); - if (slot_prev == slot_now) { - set_slot_light_color(3); - } else { - change_slot_auto(slot_prev); - } + uint8_t slot_now = payload->slot_index; + bool enable = payload->enabled; + tag_emulation_slot_set_enable(slot_now, enable); + if (!enable) { + uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); + NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); + if (slot_prev == slot_now) { + set_slot_light_color(3); + } else { + change_slot_auto(slot_prev); } - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_slot_data_config_save(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_slot_data_config_save(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_emulation_save(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_activated_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_active_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t slot = tag_emulation_get_slot(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &slot); } -data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t slot_info[16] = {}; +static data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint16_t hf_tag_type; + uint16_t lf_tag_type; + } PACKED payload[8]; + tag_specific_type_t tag_type[2]; for (uint8_t slot = 0; slot < 8; slot++) { tag_emulation_get_specific_type_by_slot(slot, tag_type); - slot_info[slot * 2] = tag_type[0]; - slot_info[slot * 2 + 1] = tag_type[1]; + payload[slot].hf_tag_type = U16HTONS(tag_type[0]); + payload[slot].lf_tag_type = U16HTONS(tag_type[1]); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 16, slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_wipe_fds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_wipe_fds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { bool success = fds_wipe(); status = success ? STATUS_DEVICE_SUCCESS : STATUS_FLASH_WRITE_FAIL; delayed_reset(50); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_set_em410x_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == LF_EM410X_TAG_ID_SIZE) { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); - memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); - tag_emulation_load_by_buffer(TAG_TYPE_EM410X, false); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_em410x_set_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != LF_EM410X_TAG_ID_SIZE) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); + memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); + tag_emulation_load_by_buffer(TAG_TYPE_EM410X, false); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_em410x_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_specific_type_t tag_type[2]; tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); if (tag_type[1] == TAG_TYPE_UNKNOWN) { @@ -485,348 +558,272 @@ data_frame_tx_t *cmd_processor_get_em410x_emu_id(uint16_t cmd, uint16_t status, return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, LF_EM410X_TAG_ID_SIZE, responseData); } -data_frame_tx_t *cmd_processor_get_mf1_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_specific_type_t tag_type[2]; tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); if (tag_type[0] == TAG_TYPE_UNKNOWN) { - return data_frame_make(cmd, STATUS_PAR_ERR, 0, data); // no data in slot, don't send garbage + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); // no data in slot, don't send garbage } - uint8_t responseData[16] = {}; nfc_tag_14a_coll_res_reference_t *info = get_saved_mifare_coll_res(); - memcpy(responseData, info->uid, *info->size); - responseData[10] = *info->size; // size is 2 byte len, but... - responseData[12] = info->sak[0]; - memcpy(&responseData[13], info->atqa, 2); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 16, responseData); -} - -data_frame_tx_t *cmd_processor_set_mf1_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_detection_log_clear(); - nfc_tag_mf1_set_detection_enable(data[0]); - status = STATUS_DEVICE_SUCCESS; + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + uint8_t payload[1 + *info->size + 2 + 1 + 1 + 254]; + uint16_t offset = 0; + payload[offset++] = *info->size; + memcpy(&payload[offset], info->uid, *info->size); + offset += *info->size; + memcpy(&payload[offset], info->atqa, 2); + offset += 2; + payload[offset++] = *info->sak; + if (info->ats->length > 0) { + payload[offset++] = info->ats->length; + memcpy(&payload[offset], info->ats->data, info->ats->length); + offset += info->ats->length; } else { - status = STATUS_PAR_ERR; + payload[offset++] = 0; } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, offset, payload); } -data_frame_tx_t *cmd_processor_get_mf1_detection_status(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_detection_enable()) { - status = 1; - } else { - status = 0; +static data_frame_tx_t *cmd_processor_mf1_set_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); + nfc_tag_mf1_detection_log_clear(); + nfc_tag_mf1_set_detection_enable(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf1_get_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t is_enable = nfc_tag_mf1_is_detection_enable(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&is_enable)); } -data_frame_tx_t *cmd_processor_get_mf1_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t count = nfc_tag_mf1_detection_log_count(); if (count == 0xFFFFFFFF) { count = 0; } - status = STATUS_DEVICE_SUCCESS; - return data_frame_make(cmd, status, sizeof(uint32_t), (uint8_t *)&count); + uint32_t payload = U32HTONL(count); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(uint32_t), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_mf1_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t count; uint32_t index; uint8_t *resp = NULL; - nfc_tag_mf1_auth_log_t *logs = get_mf1_auth_log(&count); - if (length == 4) { - if (count == 0xFFFFFFFF) { - length = 0; - status = STATUS_PAR_ERR; - } else { - index = bytes_to_num(data, 4); - // NRF_LOG_INFO("index = %d", index); - if (index < count) { - resp = (uint8_t *)(logs + index); - length = MIN(count - index, DATA_PACK_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)); - length = length * sizeof(nfc_tag_mf1_auth_log_t); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; - } - } - } else { - length = 0; - status = STATUS_PAR_ERR; + nfc_tag_mf1_auth_log_t *logs = mf1_get_auth_log(&count); + if (length != 4 || count == 0xFFFFFFFF) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + index = U32NTOHL(*(uint32_t *)data); + // NRF_LOG_INFO("index = %d", index); + if (index >= count) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, resp); + resp = (uint8_t *)(logs + index); + length = MIN(count - index, NETDATA_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)) * sizeof(nfc_tag_mf1_auth_log_t); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, length, resp); } -data_frame_tx_t *cmd_processor_set_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 0 && (((length - 1) % NFC_TAG_MF1_DATA_SIZE) == 0)) { - uint8_t block_index = data[0]; - uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; - if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) { - status = STATUS_PAR_ERR; - } else { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); - nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; - for (int i = 1, j = block_index; i < length; i += NFC_TAG_MF1_DATA_SIZE, j++) { - uint8_t *p_block = &data[i]; - memcpy(info->memory[j], p_block, NFC_TAG_MF1_DATA_SIZE); - } - status = STATUS_DEVICE_SUCCESS; - } - } else { +static data_frame_tx_t *cmd_processor_mf1_write_emu_block_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 0 || (((length - 1) % NFC_TAG_MF1_DATA_SIZE) != 0)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t block_index = data[0]; + uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; + if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) { status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); + nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; + for (int i = 1, j = block_index; i < length; i += NFC_TAG_MF1_DATA_SIZE, j++) { + uint8_t *p_block = &data[i]; + memcpy(info->memory[j], p_block, NFC_TAG_MF1_DATA_SIZE); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2) { - uint8_t block_index = data[0]; - uint8_t block_count = data[1]; - - // block_count > 32 will overflow the maximum message size - if (block_count != 0 && block_count <= 32 && (uint16_t) block_index + block_count < NFC_TAG_MF1_BLOCK_MAX) { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); - nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; - uint16_t result_length = block_count * NFC_TAG_MF1_DATA_SIZE; - uint8_t result_buffer[result_length]; - for (int i = 0, j = block_index; i < result_length; i += NFC_TAG_MF1_DATA_SIZE, j++) { - uint8_t *p_block = &result_buffer[i]; - memcpy(p_block, info->memory[j], NFC_TAG_MF1_DATA_SIZE); - } - - return data_frame_make(cmd, status, result_length, result_buffer); - } +static data_frame_tx_t *cmd_processor_mf1_read_emu_block_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (data[1] < 1) || (data[1] > 32) || (data[0] + data[1] > NFC_TAG_MF1_BLOCK_MAX)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - - return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + uint8_t block_index = data[0]; + uint8_t block_count = data[1]; + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); + nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; + uint16_t result_length = block_count * NFC_TAG_MF1_DATA_SIZE; + uint8_t result_buffer[result_length]; + for (int i = 0, j = block_index; i < result_length; i += NFC_TAG_MF1_DATA_SIZE, j++) { + memcpy(&result_buffer[i], info->memory[j], NFC_TAG_MF1_DATA_SIZE); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, result_length, result_buffer); } -data_frame_tx_t *cmd_processor_set_mf1_anti_collision_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 13) { - // sak(1) + atqa(2) + uid(10) - status = STATUS_PAR_ERR; - } else { - uint8_t uid_length = length - 3; - if (is_valid_uid_size(uid_length)) { - nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); - // copy sak - info->sak[0] = data[0]; - // copy atqa - memcpy(info->atqa, &data[1], 2); - // copy uid - memcpy(info->uid, &data[3], uid_length); - // copy size - *(info->size) = (nfc_tag_14a_uid_size)uid_length; - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; - } +static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + if ((length < 1) || \ + (!is_valid_uid_size(data[0])) || \ + (length < 1 + data[0] + 2 + 1 + 1) || \ + (length < 1 + data[0] + 2 + 1 + 1 + data[1 + data[0] + 2 + 1])) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); + uint16_t offset = 0; + *(info->size) = (nfc_tag_14a_uid_size)data[offset]; + offset++; + memcpy(info->uid, &data[offset], *(info->size)); + offset += *(info->size); + memcpy(info->atqa, &data[offset], 2); + offset += 2; + info->sak[0] = data[offset]; + offset ++; + info->ats->length = data[offset]; + offset ++; + memcpy(info->ats->data, &data[offset], info->ats->length); + offset += info->ats->length; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_tag_nick_name(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 34 || length < 3) { - status = STATUS_PAR_ERR; - } else { - uint8_t slot = data[0]; - uint8_t sense_type = data[1]; - fds_slot_record_map_t map_info; - - get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); +static data_frame_tx_t *cmd_processor_set_slot_tag_nick(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length < 3 || length > 34) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t slot = data[0]; + uint8_t sense_type = data[1]; + fds_slot_record_map_t map_info; + if (slot >= TAG_MAX_SLOT_NUM || (sense_type != TAG_SENSE_HF && sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - uint8_t buffer[36]; - buffer[0] = length - 2; - memcpy(buffer + 1, data + 2, buffer[0]); + uint8_t buffer[36]; + buffer[0] = length - 2; + memcpy(buffer + 1, data + 2, buffer[0]); - bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(buffer) / 4, buffer); - if (ret) { - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_FLASH_WRITE_FAIL; - } + bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(buffer) / 4, buffer); + if (!ret) { + return data_frame_make(cmd, STATUS_FLASH_WRITE_FAIL, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_slot_tag_nick_name(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_slot_tag_nick(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (length != 2) { - status = STATUS_PAR_ERR; - return data_frame_make(cmd, status, 0, NULL); - } else { - uint8_t buffer[36]; - uint8_t slot = data[0]; - uint8_t sense_type = data[1]; - fds_slot_record_map_t map_info; - - get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - bool ret = fds_read_sync(map_info.id, map_info.key, sizeof(buffer), buffer); - if (ret) { - status = STATUS_DEVICE_SUCCESS; - length = buffer[0]; - data = &buffer[1]; - } else { - status = STATUS_FLASH_READ_FAIL; - length = 0; - data = NULL; - } - // must be called within stack allocation of buffer - return data_frame_make(cmd, status, length, data); + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + uint8_t slot = data[0]; + uint8_t sense_type = data[1]; + uint8_t buffer[36]; + fds_slot_record_map_t map_info; + + if (slot >= TAG_MAX_SLOT_NUM || (sense_type != TAG_SENSE_HF && sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); + bool ret = fds_read_sync(map_info.id, map_info.key, sizeof(buffer), buffer); + if (!ret) { + return data_frame_make(cmd, STATUS_FLASH_READ_FAIL, 0, NULL); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, buffer[0], &buffer[1]); } -data_frame_tx_t *cmd_processor_get_mf1_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_emulator_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t mf1_info[5] = {}; mf1_info[0] = nfc_tag_mf1_is_detection_enable(); mf1_info[1] = nfc_tag_mf1_is_gen1a_magic_mode(); mf1_info[2] = nfc_tag_mf1_is_gen2_magic_mode(); mf1_info[3] = nfc_tag_mf1_is_use_mf1_coll_res(); - nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); - if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { - mf1_info[4] = 0; - } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { - mf1_info[4] = 1; - } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { - mf1_info[4] = 2; - } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { - mf1_info[4] = 3; - } + mf1_info[4] = nfc_tag_mf1_get_write_mode(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 5, mf1_info); } -data_frame_tx_t *cmd_processor_get_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_gen1a_magic_mode()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_gen1a_magic_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_gen1a_magic_mode(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_gen1a_magic_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_gen2_magic_mode()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_gen2_magic_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_gen2_magic_mode(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_gen2_magic_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_use_mf1_coll_res()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_block_anti_coll_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_use_mf1_coll_res(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_use_mf1_coll_res(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_block_anti_coll_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_use_mf1_coll_res(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); - if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { - status = 0; - } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { - status = 1; - } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { - status = 2; - } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { - status = 3; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); -} - -data_frame_tx_t *cmd_processor_set_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] >= 0 || data[0] <= 3)) { - uint8_t mode = data[0]; - if (mode == 0) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_NORMAL); - } else if (mode == 1) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DENIED); - } else if (mode == 2) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DECEIVE); - } else if (mode == 3) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_SHADOW); - } - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_get_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_get_write_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); +} + +static data_frame_tx_t *cmd_processor_mf1_set_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] > 3) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_write_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t slot_info[8] = {}; for (uint8_t slot = 0; slot < 8; slot++) { slot_info[slot] = tag_emulation_slot_is_enable(slot); } - - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(slot_info), slot_info); } -data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - return data_frame_make( - cmd, - STATUS_DEVICE_SUCCESS, - BLE_CONNECT_KEY_LEN_MAX, // 6 - settings_get_ble_connect_key() // Get key point from config - ); +static data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, BLE_PAIRING_KEY_LEN, settings_get_ble_connect_key()); } -data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == BLE_CONNECT_KEY_LEN_MAX) { - // Must be 6 ASCII characters, can only be 0-9. - bool is_valid_key = true; - for (uint8_t i = 0; i < BLE_CONNECT_KEY_LEN_MAX; i++) { - if (data[i] < 48 || data[i] > 57) { - is_valid_key = false; - break; - } - } - if (is_valid_key) { - // Key is valid, we can update to config - settings_set_ble_connect_key(data); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != BLE_PAIRING_KEY_LEN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + // Must be 6 ASCII characters, can only be 0-9. + bool is_valid_key = true; + for (uint8_t i = 0; i < BLE_PAIRING_KEY_LEN; i++) { + if (data[i] < '0' || data[i] > '9') { + is_valid_key = false; + break; } - } else { - status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + if (!is_valid_key) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + // Key is valid, we can update to config + settings_set_ble_connect_key(data); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_del_ble_all_bonds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_delete_all_ble_bonds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { advertising_stop(); delete_bonds_all(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); @@ -839,13 +836,12 @@ data_frame_tx_t *cmd_processor_del_ble_all_bonds(uint16_t cmd, uint16_t status, * before reader run, reset reader and on antenna, * we must to wait some time, to init picc(power). */ -data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { device_mode_t mode = get_device_mode(); - if (mode == DEVICE_MODE_READER) { - return NULL; - } else { + if (mode != DEVICE_MODE_READER) { return data_frame_make(cmd, STATUS_DEVICE_MODE_ERROR, 0, NULL); } + return NULL; } @@ -853,7 +849,7 @@ data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t lengt * before reader run, reset reader and on antenna, * we must to wait some time, to init picc(power). */ -data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { data_frame_tx_t *ret = before_reader_run(cmd, status, length, data); if (ret == NULL) { pcd_14a_reader_reset(); @@ -866,21 +862,31 @@ data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t le /** * after reader run, off antenna, to keep battery. */ -data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { pcd_14a_reader_antenna_off(); return NULL; } #endif +// fct will be defined after m_data_cmd_map because we need to know its size +data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); + /** * (cmd -> processor) function map, the map struct is: * cmd code before process cmd processor after process */ static cmd_data_map_t m_data_cmd_map[] = { - { DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_version, NULL }, + { DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_app_version, NULL }, { DATA_CMD_CHANGE_DEVICE_MODE, NULL, cmd_processor_change_device_mode, NULL }, { DATA_CMD_GET_DEVICE_MODE, NULL, cmd_processor_get_device_mode, NULL }, + { DATA_CMD_SET_ACTIVE_SLOT, NULL, cmd_processor_set_active_slot, NULL }, + { DATA_CMD_SET_SLOT_TAG_TYPE, NULL, cmd_processor_set_slot_tag_type, NULL }, + { DATA_CMD_SET_SLOT_DATA_DEFAULT, NULL, cmd_processor_set_slot_data_default, NULL }, + { DATA_CMD_SET_SLOT_ENABLE, NULL, cmd_processor_set_slot_enable, NULL }, + { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick, NULL }, + { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick, NULL }, + { DATA_CMD_SLOT_DATA_CONFIG_SAVE, NULL, cmd_processor_slot_data_config_save, NULL }, { DATA_CMD_ENTER_BOOTLOADER, NULL, cmd_processor_enter_bootloader, NULL }, { DATA_CMD_GET_DEVICE_CHIP_ID, NULL, cmd_processor_get_device_chip_id, NULL }, { DATA_CMD_GET_DEVICE_ADDRESS, NULL, cmd_processor_get_device_address, NULL }, @@ -889,101 +895,78 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_SET_ANIMATION_MODE, NULL, cmd_processor_set_animation_mode, NULL }, { DATA_CMD_GET_ANIMATION_MODE, NULL, cmd_processor_get_animation_mode, NULL }, { DATA_CMD_GET_GIT_VERSION, NULL, cmd_processor_get_git_version, NULL }, + { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_active_slot, NULL }, + { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, + { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, + { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, + { DATA_CMD_DELETE_SLOT_SENSE_TYPE, NULL, cmd_processor_delete_slot_sense_type, NULL }, { DATA_CMD_GET_BATTERY_INFO, NULL, cmd_processor_get_battery_info, NULL }, { DATA_CMD_GET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_button_press_config, NULL }, { DATA_CMD_SET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_button_press_config, NULL }, { DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_long_button_press_config, NULL }, { DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_long_button_press_config, NULL }, - { DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG, NULL, cmd_processor_get_ble_connect_key, NULL }, - { DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG, NULL, cmd_processor_set_ble_connect_key, NULL }, - { DATA_CMD_DELETE_ALL_BLE_BONDS, NULL, cmd_processor_del_ble_all_bonds, NULL }, - { DATA_CMD_GET_DEVICE, NULL, cmd_processor_get_device, NULL }, - { DATA_CMD_GET_SETTINGS, NULL, cmd_processor_get_settings, NULL }, - { DATA_CMD_GET_DEVICE_CAPABILITIES, NULL, NULL, NULL }, + { DATA_CMD_GET_BLE_PAIRING_KEY, NULL, cmd_processor_get_ble_connect_key, NULL }, + { DATA_CMD_SET_BLE_PAIRING_KEY, NULL, cmd_processor_set_ble_connect_key, NULL }, + { DATA_CMD_DELETE_ALL_BLE_BONDS, NULL, cmd_processor_delete_all_ble_bonds, NULL }, + { DATA_CMD_GET_DEVICE_MODEL, NULL, cmd_processor_get_device_model, NULL }, + { DATA_CMD_GET_DEVICE_SETTINGS, NULL, cmd_processor_get_device_settings, NULL }, + { DATA_CMD_GET_DEVICE_CAPABILITIES, NULL, cmd_processor_get_device_capabilities, NULL }, { DATA_CMD_GET_BLE_PAIRING_ENABLE, NULL, cmd_processor_get_ble_pairing_enable, NULL }, { DATA_CMD_SET_BLE_PAIRING_ENABLE, NULL, cmd_processor_set_ble_pairing_enable, NULL }, #if defined(PROJECT_CHAMELEON_ULTRA) - { DATA_CMD_SCAN_14A_TAG, before_hf_reader_run, cmd_processor_14a_scan, after_hf_reader_run }, - { DATA_CMD_MF1_SUPPORT_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_support, after_hf_reader_run }, - { DATA_CMD_MF1_NT_LEVEL_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_nt_level, after_hf_reader_run }, - { DATA_CMD_MF1_DARKSIDE_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_darkside, after_hf_reader_run }, + { DATA_CMD_HF14A_SCAN, before_hf_reader_run, cmd_processor_hf14a_scan, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_SUPPORT, before_hf_reader_run, cmd_processor_mf1_detect_support, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_PRNG, before_hf_reader_run, cmd_processor_mf1_detect_prng, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_DARKSIDE, before_hf_reader_run, cmd_processor_mf1_detect_darkside, after_hf_reader_run }, { DATA_CMD_MF1_DARKSIDE_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_darkside_acquire, after_hf_reader_run }, - { DATA_CMD_MF1_NT_DIST_DETECT, before_hf_reader_run, cmd_processor_mf1_nt_distance, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_NT_DIST, before_hf_reader_run, cmd_processor_mf1_detect_nt_dist, after_hf_reader_run }, { DATA_CMD_MF1_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_nested_acquire, after_hf_reader_run }, - { DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, + { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, - { DATA_CMD_SCAN_EM410X_TAG, before_reader_run, cmd_processor_em410x_scan, NULL }, - { DATA_CMD_WRITE_EM410X_TO_T5577, before_reader_run, cmd_processor_write_em410x_2_t57, NULL }, + { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, + { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, #endif - { DATA_CMD_SET_SLOT_ACTIVATED, NULL, cmd_processor_set_slot_activated, NULL }, - { DATA_CMD_SET_SLOT_TAG_TYPE, NULL, cmd_processor_set_slot_tag_type, NULL }, - { DATA_CMD_SET_SLOT_DATA_DEFAULT, NULL, cmd_processor_set_slot_data_default, NULL }, - { DATA_CMD_SET_SLOT_ENABLE, NULL, cmd_processor_set_slot_enable, NULL }, - { DATA_CMD_SLOT_DATA_CONFIG_SAVE, NULL, cmd_processor_slot_data_config_save, NULL }, - { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_activated_slot, NULL }, - { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, - { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, - { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, - { DATA_CMD_DELETE_SLOT_SENSE_TYPE, NULL, cmd_processor_delete_slot_sense_type, NULL }, - - - - { DATA_CMD_SET_EM410X_EMU_ID, NULL, cmd_processor_set_em410x_emu_id, NULL }, - { DATA_CMD_GET_EM410X_EMU_ID, NULL, cmd_processor_get_em410x_emu_id, NULL }, - - { DATA_CMD_GET_MF1_DETECTION_STATUS, NULL, cmd_processor_get_mf1_detection_status, NULL }, - { DATA_CMD_SET_MF1_DETECTION_ENABLE, NULL, cmd_processor_set_mf1_detection_enable, NULL }, - { DATA_CMD_GET_MF1_DETECTION_COUNT, NULL, cmd_processor_get_mf1_detection_count, NULL }, - { DATA_CMD_GET_MF1_DETECTION_RESULT, NULL, cmd_processor_get_mf1_detection_log, NULL }, - { DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA, NULL, cmd_processor_set_mf1_emulator_block, NULL }, - { DATA_CMD_READ_MF1_EMU_BLOCK_DATA, NULL, cmd_processor_get_mf1_emulator_block, NULL }, - { DATA_CMD_SET_MF1_ANTI_COLLISION_RES, NULL, cmd_processor_set_mf1_anti_collision_res, NULL }, - { DATA_CMD_GET_MF1_EMULATOR_CONFIG, NULL, cmd_processor_get_mf1_info, NULL }, - { DATA_CMD_GET_MF1_GEN1A_MODE, NULL, cmd_processor_get_mf1_gen1a_magic_mode, NULL }, - { DATA_CMD_SET_MF1_GEN1A_MODE, NULL, cmd_processor_set_mf1_gen1a_magic_mode, NULL }, - { DATA_CMD_GET_MF1_GEN2_MODE, NULL, cmd_processor_get_mf1_gen2_magic_mode, NULL }, - { DATA_CMD_SET_MF1_GEN2_MODE, NULL, cmd_processor_set_mf1_gen2_magic_mode, NULL }, - { DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_get_mf1_use_coll_res, NULL }, - { DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_set_mf1_use_coll_res, NULL }, - { DATA_CMD_GET_MF1_WRITE_MODE, NULL, cmd_processor_get_mf1_write_mode, NULL }, - { DATA_CMD_SET_MF1_WRITE_MODE, NULL, cmd_processor_set_mf1_write_mode, NULL }, - { DATA_CMD_GET_MF1_ANTI_COLL_DATA, NULL, cmd_processor_get_mf1_anti_coll_data, NULL }, - - { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick_name, NULL }, - { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick_name, NULL }, + { DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_write_emu_block_data, NULL }, + { DATA_CMD_HF14A_SET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_set_anti_coll_data, NULL }, + + { DATA_CMD_MF1_SET_DETECTION_ENABLE, NULL, cmd_processor_mf1_set_detection_enable, NULL }, + { DATA_CMD_MF1_GET_DETECTION_COUNT, NULL, cmd_processor_mf1_get_detection_count, NULL }, + { DATA_CMD_MF1_GET_DETECTION_LOG, NULL, cmd_processor_mf1_get_detection_log, NULL }, + { DATA_CMD_MF1_GET_DETECTION_ENABLE, NULL, cmd_processor_mf1_get_detection_enable, NULL }, + { DATA_CMD_MF1_READ_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_read_emu_block_data, NULL }, + { DATA_CMD_MF1_GET_EMULATOR_CONFIG, NULL, cmd_processor_mf1_get_emulator_config, NULL }, + { DATA_CMD_MF1_GET_GEN1A_MODE, NULL, cmd_processor_mf1_get_gen1a_mode, NULL }, + { DATA_CMD_MF1_SET_GEN1A_MODE, NULL, cmd_processor_mf1_set_gen1a_mode, NULL }, + { DATA_CMD_MF1_GET_GEN2_MODE, NULL, cmd_processor_mf1_get_gen2_mode, NULL }, + { DATA_CMD_MF1_SET_GEN2_MODE, NULL, cmd_processor_mf1_set_gen2_mode, NULL }, + { DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_get_block_anti_coll_mode, NULL }, + { DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_set_block_anti_coll_mode, NULL }, + { DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL }, + { DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL }, + { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, + + { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, + { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, }; - -data_frame_tx_t *cmd_processor_get_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - size_t count = sizeof(m_data_cmd_map) / sizeof(m_data_cmd_map[0]); +data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + size_t count = ARRAYLEN(m_data_cmd_map); uint16_t commands[count]; memset(commands, 0, count * sizeof(uint16_t)); for (size_t i = 0; i < count; i++) { - commands[i] = PP_HTONS(m_data_cmd_map[i].cmd); + commands[i] = U16HTONS(m_data_cmd_map[i].cmd); } - return data_frame_make(cmd, status, count * sizeof(uint16_t), (uint8_t *)commands); -} - - -void cmd_map_init() { - size_t count = sizeof(m_data_cmd_map) / sizeof(m_data_cmd_map[0]); - - for (size_t i = 0; i < count; i++) { - if (m_data_cmd_map[i].cmd == DATA_CMD_GET_DEVICE_CAPABILITIES) { - m_data_cmd_map[i].cmd_processor = cmd_processor_get_capabilities; - return; - } - } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, count * sizeof(uint16_t), (uint8_t *)commands); } /** @@ -991,7 +974,7 @@ void cmd_map_init() { * * @param resp data */ -void auto_response_data(data_frame_tx_t *resp) { +static void auto_response_data(data_frame_tx_t *resp) { // TODO Please select the reply source automatically according to the message source, // and do not reply by checking the validity of the link layer by layer if (is_usb_working()) { @@ -1009,9 +992,6 @@ void auto_response_data(data_frame_tx_t *resp) { void on_data_frame_received(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { data_frame_tx_t *response = NULL; bool is_cmd_support = false; - // print info - NRF_LOG_INFO("Data frame: cmd = %02x, status = %02x, length = %d", cmd, status, length); - NRF_LOG_HEXDUMP_INFO(data, length); for (int i = 0; i < ARRAY_SIZE(m_data_cmd_map); i++) { if (m_data_cmd_map[i].cmd == cmd) { is_cmd_support = true; diff --git a/firmware/application/src/app_cmd.h b/firmware/application/src/app_cmd.h index 02b963e7..538c069d 100644 --- a/firmware/application/src/app_cmd.h +++ b/firmware/application/src/app_cmd.h @@ -15,6 +15,5 @@ typedef struct { } cmd_data_map_t; void on_data_frame_received(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); -void cmd_map_init(); #endif diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 030cd387..2d4620b4 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -771,7 +771,6 @@ static void ble_passkey_init(void) { */ int main(void) { hw_connect_init(); // Remember to initialize the pins first - cmd_map_init(); // Set function in CMD map for DATA_CMD_GET_DEVICE_CAPABILITIES fds_util_init(); // Initialize fds tool settings_load_config(); // Load settings from flash diff --git a/firmware/application/src/app_status.h b/firmware/application/src/app_status.h index 44ddc010..f0848279 100644 --- a/firmware/application/src/app_status.h +++ b/firmware/application/src/app_status.h @@ -15,17 +15,6 @@ #define HF_ERR_PARITY (0x07) // IC card parity error -///////////////////////////////////////////////////////////////////// -// MIFARE status -///////////////////////////////////////////////////////////////////// -#define DARKSIDE_CANT_FIXED_NT (0x20) // Darkside, the random number cannot be fixed, this situation may appear on the UID card -#define DARKSIDE_LUCK_AUTH_OK (0x21) // Darkside, the direct verification is successful, maybe the key is just empty -#define DARKSIDE_NACK_NO_SEND (0x22) // Darkside, the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities -#define DARKSIDE_TAG_CHANGED (0x23) // Darkside, card switching in the process of running DARKSIDE, May is the two cards quickly switched -#define NESTED_TAG_IS_STATIC (0x24) // Nested, the random number of the card response is fixed -#define NESTED_TAG_IS_HARD (0x25) // Nested, the random number of the card response is unpredictable - - ///////////////////////////////////////////////////////////////////// // lf status ///////////////////////////////////////////////////////////////////// diff --git a/firmware/application/src/ble_main.c b/firmware/application/src/ble_main.c index 464caaa5..79cfb7ef 100644 --- a/firmware/application/src/ble_main.c +++ b/firmware/application/src/ble_main.c @@ -106,8 +106,8 @@ static ble_opt_t m_static_pin_option; * @details This function will set up the ble connect passkey. */ void set_ble_connect_key(uint8_t *key) { - static uint8_t passkey[BLE_CONNECT_KEY_LEN_MAX]; - memcpy(passkey, key, BLE_CONNECT_KEY_LEN_MAX); + static uint8_t passkey[BLE_PAIRING_KEY_LEN]; + memcpy(passkey, key, BLE_PAIRING_KEY_LEN); m_static_pin_option.gap_opt.passkey.p_passkey = passkey; // NRF_LOG_RAW_HEXDUMP_INFO(passkey, 6); APP_ERROR_CHECK(sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &m_static_pin_option)); @@ -331,7 +331,7 @@ static void services_init(void) { bas_init_obj.bl_cccd_wr_sec = SEC_OPEN; bas_init_obj.bl_report_rd_sec = SEC_OPEN; } - + err_code = ble_bas_init(&m_bas, &bas_init_obj); APP_ERROR_CHECK(err_code); } diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index f8595012..2bdd087c 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -10,7 +10,7 @@ #define DATA_CMD_GET_APP_VERSION (1000) #define DATA_CMD_CHANGE_DEVICE_MODE (1001) #define DATA_CMD_GET_DEVICE_MODE (1002) -#define DATA_CMD_SET_SLOT_ACTIVATED (1003) +#define DATA_CMD_SET_ACTIVE_SLOT (1003) #define DATA_CMD_SET_SLOT_TAG_TYPE (1004) #define DATA_CMD_SET_SLOT_DATA_DEFAULT (1005) #define DATA_CMD_SET_SLOT_ENABLE (1006) @@ -28,6 +28,7 @@ #define DATA_CMD_GET_ACTIVE_SLOT (1018) #define DATA_CMD_GET_SLOT_INFO (1019) #define DATA_CMD_WIPE_FDS (1020) + #define DATA_CMD_GET_ENABLED_SLOTS (1023) #define DATA_CMD_DELETE_SLOT_SENSE_TYPE (1024) #define DATA_CMD_GET_BATTERY_INFO (1025) @@ -35,11 +36,11 @@ #define DATA_CMD_SET_BUTTON_PRESS_CONFIG (1027) #define DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG (1028) #define DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG (1029) -#define DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG (1030) -#define DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG (1031) +#define DATA_CMD_SET_BLE_PAIRING_KEY (1030) +#define DATA_CMD_GET_BLE_PAIRING_KEY (1031) #define DATA_CMD_DELETE_ALL_BLE_BONDS (1032) -#define DATA_CMD_GET_DEVICE (1033) -#define DATA_CMD_GET_SETTINGS (1034) +#define DATA_CMD_GET_DEVICE_MODEL (1033) +#define DATA_CMD_GET_DEVICE_SETTINGS (1034) #define DATA_CMD_GET_DEVICE_CAPABILITIES (1035) #define DATA_CMD_GET_BLE_PAIRING_ENABLE (1036) #define DATA_CMD_SET_BLE_PAIRING_ENABLE (1037) @@ -53,14 +54,14 @@ // Range from 2000 -> 2999 // ****************************************************************** // -#define DATA_CMD_SCAN_14A_TAG (2000) -#define DATA_CMD_MF1_SUPPORT_DETECT (2001) -#define DATA_CMD_MF1_NT_LEVEL_DETECT (2002) -#define DATA_CMD_MF1_DARKSIDE_DETECT (2003) +#define DATA_CMD_HF14A_SCAN (2000) +#define DATA_CMD_MF1_DETECT_SUPPORT (2001) +#define DATA_CMD_MF1_DETECT_PRNG (2002) +#define DATA_CMD_MF1_DETECT_DARKSIDE (2003) #define DATA_CMD_MF1_DARKSIDE_ACQUIRE (2004) -#define DATA_CMD_MF1_NT_DIST_DETECT (2005) +#define DATA_CMD_MF1_DETECT_NT_DIST (2005) #define DATA_CMD_MF1_NESTED_ACQUIRE (2006) -#define DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK (2007) +#define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) // @@ -72,8 +73,8 @@ // Range from 3000 -> 3999 // ****************************************************************** // -#define DATA_CMD_SCAN_EM410X_TAG (3000) -#define DATA_CMD_WRITE_EM410X_TO_T5577 (3001) +#define DATA_CMD_EM410X_SCAN (3000) +#define DATA_CMD_EM410X_WRITE_TO_T55XX (3001) // // ****************************************************************** @@ -83,25 +84,23 @@ // Range from 4000 -> 4999 // ****************************************************************** // -#define DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA (4000) -#define DATA_CMD_SET_MF1_ANTI_COLLISION_RES (4001) -#define DATA_CMD_SET_MF1_ANTI_COLLISION_INFO (4002) -#define DATA_CMD_SET_MF1_ATS_RESOURCE (4003) -#define DATA_CMD_SET_MF1_DETECTION_ENABLE (4004) -#define DATA_CMD_GET_MF1_DETECTION_COUNT (4005) -#define DATA_CMD_GET_MF1_DETECTION_RESULT (4006) -#define DATA_CMD_GET_MF1_DETECTION_STATUS (4007) -#define DATA_CMD_READ_MF1_EMU_BLOCK_DATA (4008) -#define DATA_CMD_GET_MF1_EMULATOR_CONFIG (4009) -#define DATA_CMD_GET_MF1_GEN1A_MODE (4010) -#define DATA_CMD_SET_MF1_GEN1A_MODE (4011) -#define DATA_CMD_GET_MF1_GEN2_MODE (4012) -#define DATA_CMD_SET_MF1_GEN2_MODE (4013) -#define DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL (4014) -#define DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL (4015) -#define DATA_CMD_GET_MF1_WRITE_MODE (4016) -#define DATA_CMD_SET_MF1_WRITE_MODE (4017) -#define DATA_CMD_GET_MF1_ANTI_COLL_DATA (4018) +#define DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA (4000) +#define DATA_CMD_HF14A_SET_ANTI_COLL_DATA (4001) +#define DATA_CMD_MF1_SET_DETECTION_ENABLE (4004) +#define DATA_CMD_MF1_GET_DETECTION_COUNT (4005) +#define DATA_CMD_MF1_GET_DETECTION_LOG (4006) +#define DATA_CMD_MF1_GET_DETECTION_ENABLE (4007) +#define DATA_CMD_MF1_READ_EMU_BLOCK_DATA (4008) +#define DATA_CMD_MF1_GET_EMULATOR_CONFIG (4009) +#define DATA_CMD_MF1_GET_GEN1A_MODE (4010) +#define DATA_CMD_MF1_SET_GEN1A_MODE (4011) +#define DATA_CMD_MF1_GET_GEN2_MODE (4012) +#define DATA_CMD_MF1_SET_GEN2_MODE (4013) +#define DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE (4014) +#define DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE (4015) +#define DATA_CMD_MF1_GET_WRITE_MODE (4016) +#define DATA_CMD_MF1_SET_WRITE_MODE (4017) +#define DATA_CMD_HF14A_GET_ANTI_COLL_DATA (4018) // // ****************************************************************** @@ -114,7 +113,7 @@ // // ****************************************************************** -#define DATA_CMD_SET_EM410X_EMU_ID (5000) -#define DATA_CMD_GET_EM410X_EMU_ID (5001) +#define DATA_CMD_EM410X_SET_EMU_ID (5000) +#define DATA_CMD_EM410X_GET_EMU_ID (5001) #endif diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c index 129e7b9d..508f33f4 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -344,10 +344,11 @@ void append_mf1_auth_log_step1(bool isKeyB, bool isNested, uint8_t block, uint8_ } // Determine whether this card slot enables the detection log record if (m_tag_information->config.detection_enable) { - m_auth_log.logs[m_auth_log.count].cmd.is_key_b = isKeyB; - m_auth_log.logs[m_auth_log.count].cmd.block = block; - m_auth_log.logs[m_auth_log.count].cmd.is_nested = isNested; + m_auth_log.logs[m_auth_log.count].is_key_b = isKeyB; + m_auth_log.logs[m_auth_log.count].block = block; + m_auth_log.logs[m_auth_log.count].is_nested = isNested; memcpy(m_auth_log.logs[m_auth_log.count].uid, UID_BY_CASCADE_LEVEL, 4); +// m_auth_log.logs[m_auth_log.count].nt = U32HTONL(*(uint32_t *)nonce); memcpy(m_auth_log.logs[m_auth_log.count].nt, nonce, 4); } } @@ -363,6 +364,8 @@ void append_mf1_auth_log_step2(uint8_t *nr, uint8_t *ar) { } if (m_tag_information->config.detection_enable) { // Cache encryption information +// m_auth_log.logs[m_auth_log.count].nr = U32HTONL(*(uint32_t *)nr); +// m_auth_log.logs[m_auth_log.count].ar = U32HTONL(*(uint32_t *)ar); memcpy(m_auth_log.logs[m_auth_log.count].nr, nr, 4); memcpy(m_auth_log.logs[m_auth_log.count].ar, ar, 4); } @@ -388,7 +391,7 @@ void append_mf1_auth_log_step3(bool is_auth_success) { /** @brief MF1 obtain verification log * @param count: The statistics of the verification log */ -nfc_tag_mf1_auth_log_t *get_mf1_auth_log(uint32_t *count) { +nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count) { // First pass the total number of logs verified by verified *count = m_auth_log.count; // Just return to the head pointer of the log number array diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h index f07d0e46..d7ac34e6 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h @@ -2,6 +2,7 @@ #define NFC_MF1_H #include "nfc_14a.h" +#include "netdata.h" // Exchange space for time. // Fast simulate enable(Implement By ChameleonMini Repo) @@ -121,22 +122,23 @@ typedef struct { // MF1 label verification history typedef struct { // Basic information of verification - struct { - uint8_t block; - uint8_t is_key_b: 1; - uint8_t is_nested: 1; - // Airspace, occupying positions - uint8_t : 6; - } cmd; + uint8_t block; + uint8_t is_key_b: 1; + uint8_t is_nested: 1; + // padding to full byte + uint8_t : 6; // MFKEY32 necessary parameters uint8_t uid[4]; uint8_t nt[4]; uint8_t nr[4]; uint8_t ar[4]; -} nfc_tag_mf1_auth_log_t; + // uint32_t nt; + // uint32_t nr; + // uint32_t ar; +} PACKED nfc_tag_mf1_auth_log_t; -nfc_tag_mf1_auth_log_t *get_mf1_auth_log(uint32_t *count); +nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count); int nfc_tag_mf1_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); bool nfc_tag_mf1_data_factory(uint8_t slot, tag_specific_type_t tag_type); diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index f2c50684..25d21006 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -27,7 +27,7 @@ static picc_14a_tag_t *p_tag_info = &m_tag_info; * @retval : none * */ -void nonce_distance_notable(uint32_t *msb, uint32_t *lsb) { +static void nonce_distance(uint32_t *msb, uint32_t *lsb) { uint16_t x = 1, pos; uint8_t calc_ok = 0; @@ -61,12 +61,12 @@ void nonce_distance_notable(uint32_t *msb, uint32_t *lsb) { * false = hardened prng * */ -bool validate_prng_nonce_notable(uint32_t nonce) { +static bool check_lfsr_prng(uint32_t nonce) { // Give the initial coordinate value uint32_t msb = nonce >> 16; uint32_t lsb = nonce & 0xffff; //The coordinates are passed in direct operation, and the rumors are also indirectly spread by the passing parameters. - nonce_distance_notable(&msb, &lsb); + nonce_distance(&msb, &lsb); return ((65535 - msb + lsb) % 65535) == 16; } @@ -91,7 +91,7 @@ static inline void reset_radio_field_with_delay(void) { * @retval : The length of the card response data, this length is the length of the bit, not the length of Byte * */ -uint8_t send_cmd(struct Crypto1State *pcs, uint8_t encrypted, uint8_t cmd, uint8_t data, uint8_t *status, uint8_t *answer, uint8_t *answer_parity, uint16_t answer_max_bit) { +static uint8_t send_cmd(struct Crypto1State *pcs, uint8_t encrypted, uint8_t cmd, uint8_t data, uint8_t *status, uint8_t *answer, uint8_t *answer_parity, uint16_t answer_max_bit) { // Here we set directly to static static uint8_t pos; static uint16_t len; @@ -256,7 +256,7 @@ int authex(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t keyT * As a result, the CPU cannot complete the replay attack within the same time. * There is basically no solution in this situation. It is recommended to close the non -critical interruption and task scheduling outside this code */ -uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keytype, uint32_t *nt) { +static uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keytype, uint32_t *nt, mf1_darkside_status_t *darkside_status) { #define NT_COUNT 15 uint8_t tag_auth[4] = { keytype, block, 0x00, 0x00 }; uint8_t tag_resp[4] = { 0x00 }; @@ -315,12 +315,14 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty // If it is not greater than 0, it means that the clock cannot be synchronized. if (max == 0) { NRF_LOG_INFO("Can't sync nt.\n"); - return DARKSIDE_CANT_FIXED_NT; + *darkside_status = DARKSIDE_CANT_FIX_NT; + return HF_TAG_OK; } // NT is fixed successfully, the one with the highest number of times we take out // NRF_LOG_INFO("Sync nt: %"PRIu32", max = %d\n", nt_list[m], max); if (nt) *nt = nt_list[m]; // Only when the caller needs to get NT + *darkside_status = DARKSIDE_OK; return HF_TAG_OK; } @@ -332,7 +334,8 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty * */ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, - uint8_t firstRecover, uint8_t ntSyncMax, DarksideCore *dc) { + uint8_t firstRecover, uint8_t ntSyncMax, DarksideCore_t *dc, + mf1_darkside_status_t *darkside_status) { // Card information for fixed use static uint32_t uid_ori = 0; @@ -385,8 +388,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, uid_ori = get_u32_tag_uid(p_tag_info); // Then you need to fix a random number that may appear - status = darkside_select_nonces(p_tag_info, targetBlk, targetTyp, &nt_ori); - if (status != HF_TAG_OK) { + status = darkside_select_nonces(p_tag_info, targetBlk, targetTyp, &nt_ori, darkside_status); + if ((status != HF_TAG_OK) || (*darkside_status != DARKSIDE_OK)) { //The fixed random number failed, and the next step cannot be performed return status; } @@ -398,7 +401,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, par = par_low; if (uid_ori != uid_cur) { - return DARKSIDE_TAG_CHANGED; + *darkside_status = DARKSIDE_TAG_CHANGED; + return HF_TAG_OK; } } @@ -437,7 +441,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // In other words, this error has no chance to correct the random number if (++resync_count == ntSyncMax) { NRF_LOG_INFO("Can't fix nonce.\r\n"); - return DARKSIDE_CANT_FIXED_NT; + *darkside_status = DARKSIDE_CANT_FIX_NT; + return HF_TAG_OK; } // When the clock is not synchronized, the following operation is meaningless @@ -473,7 +478,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // did we get lucky and got our dummy key to be valid? // however we dont feed key w uid it the prng.. NRF_LOG_INFO("Auth Ok, you are so lucky!\n"); - return DARKSIDE_LUCK_AUTH_OK; + *darkside_status = DARKSIDE_LUCKY_AUTH_OK; + return HF_TAG_OK; } // Receive answer. This will be a 4 Bit NACK when the 8 parity bits are OK after decoding @@ -500,7 +506,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, par++; if (par == 0) { // tried all 256 possible parities without success. Card doesn't send NACK. NRF_LOG_INFO("Card doesn't send NACK.\r\n"); - return DARKSIDE_NACK_NO_SEND; + *darkside_status = DARKSIDE_NO_NAK_SENT; + return HF_TAG_OK; } } else { // Why this? @@ -522,6 +529,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, memcpy(dc->ar, mf_nr_ar + 4, sizeof(dc->ar)); // NRF_LOG_INFO("Darkside done!\n"); + *darkside_status = DARKSIDE_OK; return HF_TAG_OK; } @@ -539,19 +547,16 @@ void antenna_switch_delay(uint32_t delay_ms) { /** * @brief :Determine whether this card supports DARKSIDE attack -* @retval : If support, return hf_tag_ok, if it is not supported, -* Return to the results of abnormal results during the detection process: -* 1. DARKSIDE_CANT_FIXED_NT -* 2. DARKSIDE_NACK_NO_SEND -* 3. DARKSIDE_TAG_CHANGED +* @retval : If support, return hf_tag_ok and darkside_status = OK. If it is not supported, +* Return to the results of abnormal results during the detection process in darkside_status * Or other card -related communication errors, the most common is loss card HF_TAG_NO * */ -uint8_t check_darkside_support() { +uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status) { // Instantiated parameter - DarksideCore dc; + DarksideCore_t dc; //Determine and return the result directly - return darkside_recover_key(0x03, PICC_AUTHENT1A, true, 0x15, &dc); + return darkside_recover_key(0x03, PICC_AUTHENT1A, true, 0x15, &dc, darkside_status); } /** @@ -594,7 +599,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { * Lost card hf_tag_no and wrong status hf_errstat * */ -uint8_t check_std_mifare_nt_support() { +uint8_t check_std_mifare_nt_support(bool *support) { uint32_t nt1 = 0; // Find card, search on the field @@ -603,7 +608,12 @@ uint8_t check_std_mifare_nt_support() { } // Get NT - return check_tag_response_nt(p_tag_info, &nt1); + uint8_t status = check_tag_response_nt(p_tag_info, &nt1); + if (status == HF_TAG_NO) { + return HF_TAG_NO; + } + *support = status == HF_TAG_OK; + return HF_TAG_OK; } /** @@ -612,7 +622,7 @@ uint8_t check_std_mifare_nt_support() { * If support, return nested_tag_is_static, if not support, * */ -uint8_t check_static_nested_support() { +uint8_t check_static_prng(bool *is_static) { uint32_t nt1, nt2; uint8_t status; @@ -639,29 +649,31 @@ uint8_t check_static_nested_support() { } // Detect whether the random number is static - if (nt1 == nt2) { - return NESTED_TAG_IS_STATIC; - } - + *is_static = (nt1 == nt2); return HF_TAG_OK; } /** -* @brief : Determine whether this card supports the most common, weaker, and easiest nested attack +* @brief : Determine whether this card supports the most common, weaker, and easiest nested attack, or static, or hardnested * @retval : critical result * */ -uint8_t check_weak_nested_support() { +uint8_t check_prng_type(mf1_prng_type_t *prng_type) { uint8_t status; uint32_t nt1; - status = check_static_nested_support(); + bool is_static; + status = check_static_prng(&is_static); // If the judgment process is found, it is found that the StaticNested detection cannot be completed // Then return the state directly, no need to perform the following judgment logic. if (status != HF_TAG_OK) { return status; } + if (is_static) { + *prng_type = PRNG_STATIC; + return HF_TAG_OK; + } // Non -Static card, you can continue to run down logic // ------------------------------------ @@ -682,17 +694,19 @@ uint8_t check_weak_nested_support() { } //Calculate the effectiveness of NT - if (validate_prng_nonce_notable(nt1)) { + if (check_lfsr_prng(nt1)) { // NRF_LOG_INFO("The tag support Nested\n"); - return HF_TAG_OK; + *prng_type = PRNG_WEAK; + } else { + // NRF_LOG_INFO("The tag support HardNested\n"); + // NT is unpredictable and invalid. + *prng_type = PRNG_HARD; } - // NRF_LOG_INFO("The tag support HardNested\n"); - // NT is unpredictable and invalid. // ------------------------------------ // end - return NESTED_TAG_IS_HARD; + return HF_TAG_OK; } /** @@ -702,12 +716,12 @@ uint8_t check_weak_nested_support() { * @retval : Distance value * */ -uint32_t measure_nonces(uint32_t from, uint32_t to) { +static uint32_t measure_nonces(uint32_t from, uint32_t to) { // Give the initial coordinate value uint32_t msb = from >> 16; uint32_t lsb = to >> 16; // The coordinates are passed in direct operation, and the rumors are also indirectly spread by the passing parameters. - nonce_distance_notable(&msb, &lsb); + nonce_distance(&msb, &lsb); return (65535 + lsb - msb) % 65535; } @@ -753,7 +767,7 @@ uint32_t measure_median(uint32_t *src, uint32_t length) { * @retval : Operating result * */ -uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t *distance) { +static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t *distance) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint32_t distances[DIST_NR] = { 0x00 }; @@ -784,7 +798,8 @@ uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t // If you really encounter the same NT, then it can only be explained that this card is a ST card for special firmware if (nt1 == nt2) { NRF_LOG_INFO("StaticNested: %08x vs %08x\n", nt1, nt2); - return NESTED_TAG_IS_STATIC; + *distance = 0; + return HF_TAG_OK; } // After the measurement is completed, store in the buffer distances[index++] = measure_nonces(nt1, nt2); @@ -808,7 +823,7 @@ uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t * @retval : Successfully return hf_tag_ok, verify the unsuccessful return of the non -hf_tag_ok value * */ -uint8_t nested_recover_core(NestedCore *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { +static uint8_t nested_recover_core(NestedCore_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint8_t status; @@ -854,7 +869,7 @@ uint8_t nested_recover_core(NestedCore *pnc, uint64_t keyKnown, uint8_t blkKnown * @retval :The attack returns hf_tag_ok, the attack is unsuccessful to return the non -hf_tag_ok value * */ -uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore ncs[SETS_NR]) { +uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore_t ncs[SETS_NR]) { uint8_t m, res; // all operations must be based on the card res = pcd_14a_reader_scan_auto(p_tag_info); @@ -887,29 +902,19 @@ uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown * @retval : Operating status value * */ -uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, NestedDist *nd) { +uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, uint8_t *uid, uint32_t *distance) { uint8_t status = HF_TAG_OK; - uint32_t distance = 0; + *distance = 0; //Must ensure that there is a card on the court status = pcd_14a_reader_scan_auto(p_tag_info); if (status != HF_TAG_OK) { return status; } else { // At least the card exists, you can copy the UID to the buffer first - get_4byte_tag_uid(p_tag_info, nd->uid); + get_4byte_tag_uid(p_tag_info, uid); } // Get distance, prepare for the next attack - status = measure_distance( - bytes_to_num(key, 6), - block, - type, - &distance - ); - // Everything is normal, we need to put the distance value into the result - if (status == HF_TAG_OK) { - num_to_bytes(distance, 4, nd->distance); - } - return status; + return measure_distance(bytes_to_num(key, 6), block, type, distance); } /** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 01f86795..3d8496ee 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -6,7 +6,7 @@ #include #include #include - +#include "netdata.h" #define SETS_NR 2 // Using several sets of random number probes, at least two can ensure that there are two sets of random number combinations for intersection inquiries. The larger the value, the easier it is to succeed. #define DIST_NR 3 // The more distance the distance can accurately judge the communication stability of the current card @@ -18,18 +18,27 @@ #define AUTH_FIRST 0 #define AUTH_NESTED 2 -typedef struct { //Answer the distance parameters required for Nested attack - uint8_t uid[4]; //The U32 part of the UID part of this distance data - uint8_t distance[4]; //Unblocked explicitly random number -} NestedDist; - +typedef enum { + PRNG_STATIC = 0u, // the random number of the card response is fixed + PRNG_WEAK = 1u, // the random number of the card response is weak + PRNG_HARD = 2u, // the random number of the card response is unpredictable +} mf1_prng_type_t; typedef struct { //Answer the random number parameters required for Nested attack uint8_t nt1[4]; //Unblocked explicitly random number uint8_t nt2[4]; //Random number of nested verification encryption uint8_t par; //The puppet test of the communication process of nested verification encryption, only the "low 3 digits', that is, the right 3 -} NestedCore; +} NestedCore_t; + +typedef enum { + DARKSIDE_OK = 0u, // normal process + DARKSIDE_CANT_FIX_NT = 1u, // the random number cannot be fixed, this situation may appear on some UID card + DARKSIDE_LUCKY_AUTH_OK = 2u, // the direct authentification is successful, maybe the key is just the default one + DARKSIDE_NO_NAK_SENT = 3u, // the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities + DARKSIDE_TAG_CHANGED = 4u, // card swap while running DARKSIDE +} mf1_darkside_status_t; +// this struct is also used in the fw/cli protocol, therefore PACKED typedef struct { uint8_t uid[4]; uint8_t nt[4]; @@ -37,7 +46,7 @@ typedef struct { uint8_t ks_list[8]; uint8_t nr[4]; uint8_t ar[4]; -} DarksideCore; +} PACKED DarksideCore_t; #ifdef __cplusplus @@ -50,13 +59,15 @@ uint8_t darkside_recover_key( uint8_t targetTyp, uint8_t firstRecover, uint8_t ntSyncMax, - DarksideCore *dc + DarksideCore_t *dc, + mf1_darkside_status_t *darkside_status ); uint8_t nested_distance_detect( uint8_t block, uint8_t type, uint8_t *key, - NestedDist *nd + uint8_t *uid, + uint32_t *distance ); uint8_t nested_recover_key( uint64_t keyKnown, @@ -64,11 +75,11 @@ uint8_t nested_recover_key( uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, - NestedCore ncs[SETS_NR] + NestedCore_t ncs[SETS_NR] ); -uint8_t check_darkside_support(void); -uint8_t check_weak_nested_support(void); -uint8_t check_std_mifare_nt_support(void); +uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status); +uint8_t check_prng_type(mf1_prng_type_t *type); +uint8_t check_std_mifare_nt_support(bool *support); void antenna_switch_delay(uint32_t delay_ms); uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key); diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index be1493f7..2c4d00f2 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -6,6 +6,7 @@ #include #include #include +#include "netdata.h" /* * rC522CommandWord @@ -151,13 +152,14 @@ #define U8ARR_BIT_LEN(src) ((sizeof(src)) * (8)) // basicStructurePackagingOfLabelInformation +// this struct is also used in the fw/cli protocol, therefore PACKED typedef struct { uint8_t uid[10]; // theByteArrayOfTheCardNumber,TheLongest10Byte uint8_t uid_len; // theLengthOfTheCardNumber uint8_t cascade; // theAntiCollisionLevelValueIs1Representation 4Byte,2Represents7Byte,3Means10Byte uint8_t sak; // chooseToConfirm uint8_t atqa[2]; // requestResponse -} picc_14a_tag_t; +} PACKED picc_14a_tag_t; #ifdef __cplusplus extern "C" { diff --git a/firmware/application/src/settings.c b/firmware/application/src/settings.c index ddf72d96..638e9b17 100644 --- a/firmware/application/src/settings.c +++ b/firmware/application/src/settings.c @@ -44,7 +44,7 @@ void settings_init_button_long_press_config(void) { // add on version4 void settings_init_ble_connect_key_config(void) { - uint8_t p_key_u8[] = DEFAULT_BLE_CONNECT_KEY; + uint8_t p_key_u8[] = DEFAULT_BLE_PAIRING_KEY; settings_set_ble_connect_key(p_key_u8); } @@ -276,7 +276,7 @@ uint8_t *settings_get_ble_connect_key(void) { * @param key Ble connect key for your device */ void settings_set_ble_connect_key(uint8_t *key) { - memcpy(config.ble_connect_key, key, BLE_CONNECT_KEY_LEN_MAX); + memcpy(config.ble_connect_key, key, BLE_PAIRING_KEY_LEN); } void settings_set_ble_pairing_enable(bool enable) { diff --git a/firmware/application/src/settings.h b/firmware/application/src/settings.h index 1a26bf17..adfd3cd1 100644 --- a/firmware/application/src/settings.h +++ b/firmware/application/src/settings.h @@ -6,8 +6,8 @@ #include "utils.h" #define SETTINGS_CURRENT_VERSION 5 -#define BLE_CONNECT_KEY_LEN_MAX 6 -#define DEFAULT_BLE_CONNECT_KEY "123456" // length must == 6 +#define BLE_PAIRING_KEY_LEN 6 +#define DEFAULT_BLE_PAIRING_KEY "123456" // length must == 6 typedef enum { SettingsAnimationModeFull = 0U, diff --git a/firmware/application/src/utils/dataframe.c b/firmware/application/src/utils/dataframe.c index 0b5c9619..15a088e6 100644 --- a/firmware/application/src/utils/dataframe.c +++ b/firmware/application/src/utils/dataframe.c @@ -1,6 +1,5 @@ #include "dataframe.h" -#include "hex_utils.h" - +#include "netdata.h" #define NRF_LOG_MODULE_NAME data_frame #include "nrf_log.h" @@ -8,40 +7,26 @@ #include "nrf_log_default_backends.h" NRF_LOG_MODULE_REGISTER(); - -/* - * ********************************************************************************************************************************* - * Variable length data frame format - * Designed by proxgrind - * Date: 20221205 - * - * 0 1 2 3 45 6 7 8 8 + n 8 + n + 1 - * SOF(1byte) LRC(1byte) CMD(2byte) Status(2byte) Data Length(2byte) Frame Head LRC(1byte) Data(length) Frame All LRC(1byte) - * 0x11 0xEF cmd(u16) status(u16) length(u16) lrc(u8) data(u8*) lrc(u8) - * - * The data length max is 512, frame length max is 1 + 1 + 2 + 2 + 2 + 1 + n + 1 = (10 + n) - * So, one frame will than 10 byte. - * ********************************************************************************************************************************* - */ - -#define DATA_PACK_TRANSMISSION_ON 0x11 -#define DATA_LRC_CUT(val) ((uint8_t)(0x100 - val)) - - -static uint8_t m_data_rx_buffer[DATA_PACK_MAX_DATA_LENGTH + DATA_PACK_BASE_LENGTH]; -static uint8_t m_data_tx_buffer[DATA_PACK_MAX_DATA_LENGTH + DATA_PACK_BASE_LENGTH]; +static netdata_frame_raw_t m_netdata_frame_rx_buf; +static netdata_frame_raw_t m_netdata_frame_tx_buf; +static data_frame_tx_t m_frame_tx_buf_info = { + .buffer = (uint8_t *) &m_netdata_frame_tx_buf, // default buffer +}; static uint16_t m_data_rx_position = 0; -static uint8_t m_data_rx_lrc = 0; static uint16_t m_data_cmd; static uint16_t m_data_status; static uint16_t m_data_len; static uint8_t *m_data_buffer; static volatile bool m_data_completed = false; static data_frame_cbk_t m_frame_process_cbk = NULL; -static data_frame_tx_t m_frame_tx_buf_info = { - .buffer = m_data_tx_buffer, // default buffer -}; +static uint8_t compute_lrc(uint8_t *buf, uint16_t bufsize) { + uint8_t lrc = 0x00; + for (uint16_t i = 0; i < bufsize; i++) { + lrc += buf[i]; + } + return 0x100 - lrc; +} /** * @brief: create a packet, put the created data packet into the buffer, and wait for the post to set up a non busy state @@ -50,38 +35,41 @@ static data_frame_tx_t m_frame_tx_buf_info = { * @param length: answerDataLength * @param data: answerData */ -data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t lrc_tx = 0x00; - uint16_t i, j; +data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t data_length, uint8_t *data) { + if (data_length > 0 && data == NULL) { + NRF_LOG_ERROR("data_frame_make error, null pointer."); + return NULL; + } + if (data_length > 512) { + NRF_LOG_ERROR("data_frame_make error, too much data."); + return NULL; + } + NRF_LOG_INFO("TX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", cmd, cmd, status, data_length, data_length > 0 ? ", data =" : ""); + if (data_length > 0) { + NRF_LOG_HEXDUMP_INFO(data, data_length); + } + + netdata_frame_postamble_t *tx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_tx_buf + sizeof(netdata_frame_preamble_t) + data_length); // sof - m_frame_tx_buf_info.buffer[0] = DATA_PACK_TRANSMISSION_ON; + m_netdata_frame_tx_buf.pre.sof = NETDATA_FRAME_SOF; // sof lrc - lrc_tx += m_frame_tx_buf_info.buffer[0]; - m_frame_tx_buf_info.buffer[1] = DATA_LRC_CUT(lrc_tx); - lrc_tx += m_frame_tx_buf_info.buffer[1]; + m_netdata_frame_tx_buf.pre.lrc1 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.pre, offsetof(netdata_frame_preamble_t, lrc1)); // cmd - num_to_bytes(cmd, 2, &m_frame_tx_buf_info.buffer[2]); + m_netdata_frame_tx_buf.pre.cmd = U16HTONS(cmd); // status - num_to_bytes(status, 2, &m_frame_tx_buf_info.buffer[4]); - // length - num_to_bytes(length, 2, &m_frame_tx_buf_info.buffer[6]); + m_netdata_frame_tx_buf.pre.status = U16HTONS(status); + // data_length + m_netdata_frame_tx_buf.pre.len = U16HTONS(data_length); // head lrc - for (i = 2; i < 8; i++) { - lrc_tx += m_frame_tx_buf_info.buffer[i]; - } - m_frame_tx_buf_info.buffer[8] = DATA_LRC_CUT(lrc_tx); - lrc_tx += m_frame_tx_buf_info.buffer[8]; + m_netdata_frame_tx_buf.pre.lrc2 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.pre, offsetof(netdata_frame_preamble_t, lrc2)); // data - if (length > 0 && data != NULL) { - for (i = 9, j = 0; j < length; i++, j++) { - m_frame_tx_buf_info.buffer[i] = data[j]; - lrc_tx += m_frame_tx_buf_info.buffer[i]; - } + if (data_length > 0) { + memcpy(&m_netdata_frame_tx_buf.data, data, data_length); } // length out. - m_frame_tx_buf_info.length = (length + DATA_PACK_BASE_LENGTH); + m_frame_tx_buf_info.length = (sizeof(netdata_frame_preamble_t) + data_length + sizeof(netdata_frame_postamble_t)); // data all lrc - m_data_tx_buffer[m_frame_tx_buf_info.length - 1] = DATA_LRC_CUT(lrc_tx);; + tx_post->lrc3 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.data, data_length); return (&m_frame_tx_buf_info); } @@ -90,7 +78,6 @@ data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t length, */ void data_frame_reset(void) { m_data_rx_position = 0; - m_data_rx_lrc = 0; } /** @@ -105,7 +92,7 @@ void data_frame_receive(uint8_t *data, uint16_t length) { return; } // buffer overflow - if (m_data_rx_position + length >= sizeof(m_data_rx_buffer)) { + if (m_data_rx_position + length >= sizeof(m_netdata_frame_rx_buf)) { NRF_LOG_ERROR("Data frame wait overflow."); data_frame_reset(); return; @@ -113,50 +100,52 @@ void data_frame_receive(uint8_t *data, uint16_t length) { // frame process for (int i = 0; i < length; i++) { // copy to buffer - m_data_rx_buffer[m_data_rx_position] = data[i]; - if (m_data_rx_position < 2) { // start of frame - if (m_data_rx_position == 0) { - if (m_data_rx_buffer[m_data_rx_position] != DATA_PACK_TRANSMISSION_ON) { - // not sof byte - NRF_LOG_ERROR("Data frame no sof byte."); - data_frame_reset(); - return; - } + ((uint8_t *)(&m_netdata_frame_rx_buf))[m_data_rx_position] = data[i]; + if (m_data_rx_position == offsetof(netdata_frame_preamble_t, sof)) { + if (m_netdata_frame_rx_buf.pre.sof != NETDATA_FRAME_SOF) { + // not sof byte + NRF_LOG_ERROR("Data frame no sof byte."); + data_frame_reset(); + return; } - if (m_data_rx_position == 1) { - if (m_data_rx_buffer[m_data_rx_position] != DATA_LRC_CUT(m_data_rx_lrc)) { - // not sof lrc byte - NRF_LOG_ERROR("Data frame sof lrc error."); - data_frame_reset(); - return; - } + } else if (m_data_rx_position == offsetof(netdata_frame_preamble_t, lrc1)) { + if (m_netdata_frame_rx_buf.pre.lrc1 != compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.pre, offsetof(netdata_frame_preamble_t, lrc1))) { + // not sof lrc byte + NRF_LOG_ERROR("Data frame sof lrc error."); + data_frame_reset(); + return; } - } else if (m_data_rx_position == 8) { // frame head lrc - if (m_data_rx_buffer[m_data_rx_position] != DATA_LRC_CUT(m_data_rx_lrc)) { + } else if (m_data_rx_position == offsetof(netdata_frame_preamble_t, lrc2)) { // frame head lrc + if (m_netdata_frame_rx_buf.pre.lrc2 != compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.pre, offsetof(netdata_frame_preamble_t, lrc2))) { // frame head lrc error NRF_LOG_ERROR("Data frame head lrc error."); data_frame_reset(); return; } // frame head complete, cache info - m_data_cmd = bytes_to_num(&m_data_rx_buffer[2], 2); - m_data_status = bytes_to_num(&m_data_rx_buffer[4], 2); - m_data_len = bytes_to_num(&m_data_rx_buffer[6], 2); + m_data_cmd = U16NTOHS(m_netdata_frame_rx_buf.pre.cmd); + m_data_status = U16NTOHS(m_netdata_frame_rx_buf.pre.status); + m_data_len = U16NTOHS(m_netdata_frame_rx_buf.pre.len); NRF_LOG_INFO("Data frame data length %d.", m_data_len); // check data length - if (m_data_len > DATA_PACK_MAX_DATA_LENGTH) { - NRF_LOG_ERROR("Data frame data length too than of max."); + if (m_data_len > NETDATA_MAX_DATA_LENGTH) { + NRF_LOG_ERROR("Data frame data length larger than max."); data_frame_reset(); return; } - } else if (m_data_rx_position > 8) { // frame data + } else if (m_data_rx_position >= offsetof(netdata_frame_raw_t, data)) { // frame data // check all data ready. - if (m_data_rx_position == (8 + m_data_len + 1)) { - if (m_data_rx_buffer[m_data_rx_position] == DATA_LRC_CUT(m_data_rx_lrc)) { + if (m_data_rx_position == (sizeof(netdata_frame_preamble_t) + m_data_len)) { + netdata_frame_postamble_t *rx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_rx_buf + sizeof(netdata_frame_preamble_t) + m_data_len); + if (rx_post->lrc3 == compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.data, m_data_len)) { // ok, lrc for data is check success. // and we are receive completed - m_data_buffer = m_data_len > 0 ? &m_data_rx_buffer[9] : NULL; + m_data_buffer = m_data_len > 0 ? (uint8_t *)&m_netdata_frame_rx_buf.data : NULL; m_data_completed = true; + NRF_LOG_INFO("RX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", m_data_cmd, m_data_cmd, m_data_status, m_data_len, m_data_len > 0 ? ", data =" : ""); + if (m_data_len > 0) { + NRF_LOG_HEXDUMP_INFO(m_data_buffer, m_data_len); + } } else { // data frame lrc error NRF_LOG_ERROR("Data frame finally lrc error."); @@ -165,8 +154,6 @@ void data_frame_receive(uint8_t *data, uint16_t length) { return; } } - // calculate lrc - m_data_rx_lrc += data[i]; // index update m_data_rx_position++; } diff --git a/firmware/application/src/utils/dataframe.h b/firmware/application/src/utils/dataframe.h index d0e22f5d..87dba08b 100644 --- a/firmware/application/src/utils/dataframe.h +++ b/firmware/application/src/utils/dataframe.h @@ -1,23 +1,18 @@ -#ifndef DATA_PACK_H -#define DATA_PACK_H +#ifndef DATAFRAME_H +#define DATAFRAME_H #include #include - -#define DATA_PACK_MAX_DATA_LENGTH 512 -#define DATA_PACK_BASE_LENGTH 10 - - // Data frame process callback typedef void (*data_frame_cbk_t)(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); + // TX buffer typedef struct { uint8_t *const buffer; uint16_t length; } data_frame_tx_t; - void data_frame_receive(uint8_t *data, uint16_t length); void data_frame_process(void); void on_data_frame_complete(data_frame_cbk_t callback); @@ -30,4 +25,4 @@ data_frame_tx_t *data_frame_make( ); -#endif +#endif // DATAFRAME_H diff --git a/firmware/application/src/utils/netdata.h b/firmware/application/src/utils/netdata.h new file mode 100644 index 00000000..b9451ff5 --- /dev/null +++ b/firmware/application/src/utils/netdata.h @@ -0,0 +1,65 @@ +#ifndef NETDATA_H +#define NETDATA_H + +#include +#include + +#define PACKED __attribute__((packed)) + +#ifndef ARRAYLEN +# define ARRAYLEN(x) (sizeof(x)/sizeof((x)[0])) +#endif + +// nrf52840 platform is little endian +#define U16HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) +#define U16NTOHS(x) U16HTONS(x) +#define U32HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ + (((x) & (uint32_t)0x0000ff00UL) << 8) | \ + (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ + (((x) & (uint32_t)0xff000000UL) >> 24)) +#define U32NTOHL(x) U32HTONL(x) + +#define NETDATA_MAX_DATA_LENGTH 512 + +/* + * ********************************************************************************************************************************* + * Variable length data frame format + * Designed by proxgrind + * Date: 20221205 + * + * 0 1 2 3 45 6 7 8 8 + n 8 + n + 1 + * SOF(1byte) LRC(1byte) CMD(2byte) Status(2byte) Data Length(2byte) Frame Head LRC(1byte) Data(length) Frame All LRC(1byte) + * 0x11 0xEF cmd(u16) status(u16) length(u16) lrc(u8) data(u8*) lrc(u8) + * + * The data length max is 512, frame length is 1 + 1 + 2 + 2 + 2 + 1 + n + 1 = (10 + n) + * So, one frame will be between 10 and 522 bytes. + * ********************************************************************************************************************************* + */ + +// data frame preamble as sent from/to the client, Network byte order. + +typedef struct { + uint8_t sof; + uint8_t lrc1; + uint16_t cmd; + uint16_t status; + uint16_t len; + uint8_t lrc2; +} PACKED netdata_frame_preamble_t; + +#define NETDATA_FRAME_SOF 0x11 + +typedef struct { + uint8_t lrc3; +} PACKED netdata_frame_postamble_t; + +// For reception and CRC check +typedef struct { + netdata_frame_preamble_t pre; + uint8_t data[NETDATA_MAX_DATA_LENGTH]; + netdata_frame_postamble_t foopost; // Probably not at that offset! +} PACKED netdata_frame_raw_t; + +// Command-specific structs are defined in their respective cmd_processor handlers in app_cmd.c + +#endif /* NETDATA_H */ diff --git a/firmware/common/hw_connect.c b/firmware/common/hw_connect.c index bb86bd54..7d2d59e4 100644 --- a/firmware/common/hw_connect.c +++ b/firmware/common/hw_connect.c @@ -107,9 +107,6 @@ void hw_connect_init(void) { #if defined(PROJECT_CHAMELEON_ULTRA) if (m_hw_ver == 1) { LED_FIELD = (NRF_GPIO_PIN_MAP(1, 1)); - LED_R = (NRF_GPIO_PIN_MAP(0, 24)); - LED_G = (NRF_GPIO_PIN_MAP(0, 22)); - LED_B = (NRF_GPIO_PIN_MAP(1, 0)); LED_1 = (NRF_GPIO_PIN_MAP(0, 20)); LED_2 = (NRF_GPIO_PIN_MAP(0, 17)); LED_3 = (NRF_GPIO_PIN_MAP(0, 15)); @@ -118,15 +115,26 @@ void hw_connect_init(void) { LED_6 = (NRF_GPIO_PIN_MAP(1, 9)); LED_7 = (NRF_GPIO_PIN_MAP(0, 8)); LED_8 = (NRF_GPIO_PIN_MAP(0, 6)); + LED_R = (NRF_GPIO_PIN_MAP(0, 24)); + LED_G = (NRF_GPIO_PIN_MAP(0, 22)); + LED_B = (NRF_GPIO_PIN_MAP(1, 0)); RGB_LIST_NUM = 8; RGB_CTRL_NUM = 3; - LF_ANT_DRIVER = (NRF_GPIO_PIN_MAP(0, 31)); - LF_OA_OUT = (NRF_GPIO_PIN_MAP(0, 29)); LF_MOD = (NRF_GPIO_PIN_MAP(1, 13)); LF_RSSI_PIN = (NRF_GPIO_PIN_MAP(0, 2)); LF_RSSI = NRF_LPCOMP_INPUT_0; + BUTTON_1 = (NRF_GPIO_PIN_MAP(1, 2)); + BUTTON_2 = (NRF_GPIO_PIN_MAP(0, 26)); + + BAT_SENSE_PIN = (NRF_GPIO_PIN_MAP(0, 4)); + BAT_SENSE = NRF_SAADC_INPUT_AIN2; + + // Ultra only + LF_ANT_DRIVER = (NRF_GPIO_PIN_MAP(0, 31)); + LF_OA_OUT = (NRF_GPIO_PIN_MAP(0, 29)); + HF_SPI_SELECT = (NRF_GPIO_PIN_MAP(1, 6)); HF_SPI_MISO = (NRF_GPIO_PIN_MAP(0, 11)); HF_SPI_MOSI = (NRF_GPIO_PIN_MAP(1, 7)); @@ -134,12 +142,6 @@ void hw_connect_init(void) { HF_ANT_SEL = (NRF_GPIO_PIN_MAP(1, 10)); READER_POWER = (NRF_GPIO_PIN_MAP(1, 15)); - - BUTTON_2 = (NRF_GPIO_PIN_MAP(0, 26)); - BUTTON_1 = (NRF_GPIO_PIN_MAP(1, 2)); - - BAT_SENSE_PIN = (NRF_GPIO_PIN_MAP(0, 4)); - BAT_SENSE = NRF_SAADC_INPUT_AIN2; } #endif diff --git a/firmware/common/lwip_def.h b/firmware/common/lwip_def.h deleted file mode 100644 index 5559a082..00000000 --- a/firmware/common/lwip_def.h +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file - * various utility macros - */ - -/* - * Copyright (c) 2001-2004 Swedish Institute of Computer Science. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * This file is part of the lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ - -/** - * @defgroup perf Performance measurement - * @ingroup sys_layer - * All defines related to this section must not be placed in lwipopts.h, - * but in arch/perf.h! - * Measurement calls made throughout lwip, these can be defined to nothing. - * - PERF_START: start measuring something. - * - PERF_STOP(x): stop measuring something, and record the result. - */ - -#ifndef LWIP_HDR_DEF_H -#define LWIP_HDR_DEF_H - -/* arch.h might define NULL already */ -#define PERF_START /* null definition */ -#define PERF_STOP(x) /* null definition */ - -#ifdef __cplusplus -extern "C" { -#endif - -#define LWIP_MAX(x , y) (((x) > (y)) ? (x) : (y)) -#define LWIP_MIN(x , y) (((x) < (y)) ? (x) : (y)) - -/* Get the number of entries in an array ('x' must NOT be a pointer!) */ -#define LWIP_ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0])) - -/** Create uint32_t value from bytes */ -#define LWIP_MAKEU32(a,b,c,d) (((uint32_t)((a) & 0xff) << 24) | \ - ((uint32_t)((b) & 0xff) << 16) | \ - ((uint32_t)((c) & 0xff) << 8) | \ - (uint32_t)((d) & 0xff)) - -#ifndef NULL -#ifdef __cplusplus -#define NULL 0 -#else -#define NULL ((void *)0) -#endif -#endif - -#if BYTE_ORDER == BIG_ENDIAN -#define lwip_htons(x) ((uint16_t)(x)) -#define lwip_ntohs(x) ((uint16_t)(x)) -#define lwip_htonl(x) ((uint32_t)(x)) -#define lwip_ntohl(x) ((uint32_t)(x)) -#define PP_HTONS(x) ((uint16_t)(x)) -#define PP_NTOHS(x) ((uint16_t)(x)) -#define PP_HTONL(x) ((uint32_t)(x)) -#define PP_NTOHL(x) ((uint32_t)(x)) -#else /* BYTE_ORDER != BIG_ENDIAN */ -#ifndef lwip_htons -uint16_t lwip_htons(uint16_t x); -#endif -#define lwip_ntohs(x) lwip_htons(x) - -#ifndef lwip_htonl -uint32_t lwip_htonl(uint32_t x); -#endif -#define lwip_ntohl(x) lwip_htonl(x) - -/* These macros should be calculated by the preprocessor and are used - with compile-time constants only (so that there is no little-endian - overhead at runtime). */ -#define PP_HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) -#define PP_NTOHS(x) PP_HTONS(x) -#define PP_HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ - (((x) & (uint32_t)0x0000ff00UL) << 8) | \ - (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ - (((x) & (uint32_t)0xff000000UL) >> 24)) -#define PP_NTOHL(x) PP_HTONL(x) -#endif /* BYTE_ORDER == BIG_ENDIAN */ - -/* Functions that are not available as standard implementations. - * In cc.h, you can #define these to implementations available on - * your platform to save some code bytes if you use these functions - * in your application, too. - */ - -#ifndef lwip_itoa -/* This can be #defined to itoa() or snprintf(result, bufsize, "%d", number) depending on your platform */ -void lwip_itoa(char *result, size_t bufsize, int number); -#endif -#ifndef lwip_strnicmp -/* This can be #defined to strnicmp() or strncasecmp() depending on your platform */ -int lwip_strnicmp(const char *str1, const char *str2, size_t len); -#endif -#ifndef lwip_stricmp -/* This can be #defined to stricmp() or strcasecmp() depending on your platform */ -int lwip_stricmp(const char *str1, const char *str2); -#endif -#ifndef lwip_strnstr -/* This can be #defined to strnstr() depending on your platform */ -char *lwip_strnstr(const char *buffer, const char *token, size_t n); -#endif -#ifndef lwip_strnistr -/* This can be #defined to strnistr() depending on your platform */ -char *lwip_strnistr(const char *buffer, const char *token, size_t n); -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* LWIP_HDR_DEF_H */ diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 440e22f9..ce031bae 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -12,17 +12,37 @@ import chameleon_com import chameleon_cmd -import chameleon_cstruct import chameleon_status from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError from chameleon_utils import CLITree +# Colorama shorthands +CR = colorama.Fore.RED +CG = colorama.Fore.GREEN +CC = colorama.Fore.CYAN +CY = colorama.Fore.YELLOW +C0 = colorama.Style.RESET_ALL + +# NXP IDs based on https://www.nxp.com/docs/en/application-note/AN10833.pdf +type_id_SAK_dict = {0x00: "MIFARE Ultralight Classic/C/EV1/Nano | NTAG 2xx", + 0x08: "MIFARE Classic 1K | Plus SE 1K | Plug S 2K | Plus X 2K", + 0x09: "MIFARE Mini 0.3k", + 0x10: "MIFARE Plus 2K", + 0x11: "MIFARE Plus 4K", + 0x18: "MIFARE Classic 4K | Plus S 4K | Plus X 4K", + 0x19: "MIFARE Classic 2K", + 0x20: "MIFARE Plus EV1/EV2 | DESFire EV1/EV2/EV3 | DESFire Light | NTAG 4xx | MIFARE Plus S 2/4K | MIFARE Plus X 2/4K | MIFARE Plus SE 1K", + 0x28: "SmartMX with MIFARE Classic 1K", + 0x38: "SmartMX with MIFARE Classic 4K", + } + class BaseCLIUnit: def __init__(self): # new a device command transfer and receiver instance(Send cmd and receive response) self._device_com: chameleon_com.ChameleonCom | None = None + self._device_cmd: chameleon_cmd.ChameleonCMD = chameleon_cmd.ChameleonCMD(self._device_com) @property def device_com(self) -> chameleon_com.ChameleonCom: @@ -31,10 +51,11 @@ def device_com(self) -> chameleon_com.ChameleonCom: @device_com.setter def device_com(self, com): self._device_com = com + self._device_cmd = chameleon_cmd.ChameleonCMD(self._device_com) @property def cmd(self) -> chameleon_cmd.ChameleonCMD: - return chameleon_cmd.ChameleonCMD(self.device_com) + return self._device_cmd def args_parser(self) -> ArgumentParserNoExit or None: """ @@ -139,7 +160,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def before_exec(self, args: argparse.Namespace): if super(ReaderRequiredUnit, self).before_exec(args): - ret = self.cmd.is_reader_device_mode() + ret = self.cmd.is_device_reader_mode() if ret: return True else: @@ -219,9 +240,14 @@ def on_exec(self, args: argparse.Namespace): print("Chameleon not found, please connect the device or try connecting manually with the -p flag.") return self.device_com.open(args.port) - print(" { Chameleon connected } ") + self.device_com.commands = self.cmd.get_device_capabilities() + major, minor = self.cmd.get_app_version() + model = 'Ultra' if self.cmd.get_device_model() else 'Lite' + print(f" {{ Chameleon {model} connected: v{major}.{minor} }}") + except Exception as e: - print(f"Chameleon Connect fail: {str(e)}") + print(f"{CR}Chameleon Connect fail: {str(e)}{C0}") + self.device_com.close() @hw_mode.command('set', 'Change device mode to tag reader or tag emulator') @@ -235,10 +261,10 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): if args.mode == 'reader' or args.mode == 'r': - self.cmd.set_reader_device_mode(True) + self.cmd.set_device_reader_mode(True) print("Switch to { Tag Reader } mode successfully.") else: - self.cmd.set_reader_device_mode(False) + self.cmd.set_device_reader_mode(False) print("Switch to { Tag Emulator } mode successfully.") @@ -248,7 +274,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def on_exec(self, args: argparse.Namespace): - print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_reader_device_mode() else 'Emulator'} )") + print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_device_reader_mode() else 'Emulator'} )") @hw_chipid.command('get', 'Get device chipset ID') @@ -275,10 +301,11 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - fw_version_int = self.cmd.get_firmware_version() - fw_version = f'v{fw_version_int // 256}.{fw_version_int % 256}' + fw_version_tuple = self.cmd.get_app_version() + fw_version = f'v{fw_version_tuple[0]}.{fw_version_tuple[1]}' git_version = self.cmd.get_git_version() - print(f' - Version: {fw_version} ({git_version})') + model = 'Ultra' if self.cmd.get_device_model() else 'Lite' + print(f' - Chameleon {model}, Version: {fw_version} ({git_version})') @hf_14a.command('scan', 'Scan 14a tag, and print basic information') @@ -286,21 +313,43 @@ class HF14AScan(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: pass - def scan(self): - resp: chameleon_com.Response = self.cmd.scan_tag_14a() - if resp.status == chameleon_status.Device.HF_TAG_OK: - info = chameleon_cstruct.parse_14a_scan_tag_result(resp.data) - print(f"- UID Size: {info['uid_size']}") - print(f"- UID Hex : {info['uid_hex'].upper()}") - print(f"- SAK Hex : {info['sak_hex'].upper()}") - print(f"- ATQA Hex : {info['atqa_hex'].upper()}") - return True + def check_mf1_nt(self): + # detect mf1 support + if self.cmd.mf1_detect_support(): + # detect prng + print("- Mifare Classic technology") + prng_type = self.cmd.mf1_detect_prng() + print(f" # Prng: {chameleon_cmd.MifareClassicPrngType(prng_type)}") + + def sak_info(self, data_tag): + # detect the technology in use based on SAK + int_sak = data_tag['sak'][0] + if int_sak in type_id_SAK_dict: + print(f"- Guessed type(s) from SAK: {type_id_SAK_dict[int_sak]}") + + def scan(self, deep=False): + resp = self.cmd.hf14a_scan() + if resp is not None: + for data_tag in resp: + print(f"- UID : {data_tag['uid'].hex().upper()}") + print(f"- ATQA : {data_tag['atqa'].hex().upper()}") + print(f"- SAK : {data_tag['sak'].hex().upper()}") + if len(data_tag['ats']) > 0: + print(data_tag['ats']) + print(f"- ATS : {data_tag['ats'].hex().upper()}") + if deep: + self.sak_info(data_tag) + # TODO: following checks cannot be done yet if multiple cards are present + if len(resp) == 1: + self.check_mf1_nt() + # TODO: check for ATS support on 14A3 tags + else: + print("Multiple tags detected, skipping deep tests...") else: print("ISO14443-A Tag no found") - return False def on_exec(self, args: argparse.Namespace): - return self.scan() + self.scan() @hf_14a.command('info', 'Scan 14a tag, and print detail information') @@ -308,29 +357,10 @@ class HF14AInfo(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: pass - def info(self): - # detect mf1 support - resp = self.cmd.detect_mf1_support() - if resp.status == chameleon_status.Device.HF_TAG_OK: - # detect prng - print("- Mifare Classic technology") - resp = self.cmd.detect_mf1_nt_level() - if resp.status == 0x00: - prng_level = "Weak" - elif resp.status == 0x24: - prng_level = "Static" - elif resp.status == 0x25: - prng_level = "Hard" - else: - prng_level = "Unknown" - print(f" # Prng attack: {prng_level}") - def on_exec(self, args: argparse.Namespace): - # reused scan = HF14AScan() scan.device_com = self.device_com - if scan.scan(): - self.info() + scan.scan(deep=1) @hf_mf.command('nested', 'Mifare Classic nested recover key') @@ -363,11 +393,8 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t :return: """ # acquire - dist_resp = self.cmd.detect_nt_distance(block_known, type_known, key_known) - nt_resp = self.cmd.acquire_nested(block_known, type_known, key_known, block_target, type_target) - # parse - dist_obj = chameleon_cstruct.parse_nt_distance_detect_result(dist_resp.data) - nt_obj = chameleon_cstruct.parse_nested_nt_acquire_group(nt_resp.data) + dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) + nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) # create cmd cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" for nt_item in nt_obj: @@ -376,13 +403,15 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t cmd_recover = f"nested.exe {cmd_param}" else: cmd_recover = f"./nested {cmd_param}" + print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) # wait end while process.is_running(): - msg = f" [ Time elapsed {process.get_time_distance()}ms ]\r" + msg = f" [ Time elapsed {process.get_time_distance()/1000:#.1f}s ]\r" print(msg, end="") + time.sleep(0.1) # clear \r print() @@ -395,11 +424,10 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t key_list.append(sea_obj[1]) # Here you have to verify the password first, and then get the one that is successfully verified # If there is no verified password, it means that the recovery failed, you can try again - print(f" - [{len(key_list)} candidate keys found ]") + print(f" - [{len(key_list)} candidate key(s) found ]") for key in key_list: key_bytes = bytearray.fromhex(key) - ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) - if ret.status == chameleon_status.Device.HF_TAG_OK: + if self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes): return key else: # No keys recover, and no errors. @@ -422,7 +450,7 @@ def on_exec(self, args: argparse.Namespace): type_target = args.type_target if block_target is not None and type_target is not None: type_target = 0x60 if type_target == 'A' or type_target == 'a' else 0x61 - print(f" - {colorama.Fore.CYAN}Nested recover one key running...{colorama.Style.RESET_ALL}") + print(f" - {C0}Nested recover one key running...{C0}") key = self.recover_a_key(block_known, type_known, key_known, block_target, type_target) if key is None: print("No keys found, you can retry recover.") @@ -456,9 +484,12 @@ def recover_key(self, block_target, type_target): first_recover = True retry_count = 0 while retry_count < 0xFF: - darkside_resp = self.cmd.acquire_darkside(block_target, type_target, first_recover, 15) + darkside_resp = self.cmd.mf1_darkside_acquire(block_target, type_target, first_recover, 15) first_recover = False # not first run. - darkside_obj = chameleon_cstruct.parse_darkside_acquire_result(darkside_resp.data) + if darkside_resp[0] != chameleon_cmd.MifareClassicDarksideStatus.OK: + print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") + break + darkside_obj = darkside_resp[1] self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" for darkside_item in self.darkside_list: @@ -489,7 +520,7 @@ def recover_key(self, block_target, type_target): # auth key for key in key_list: key_bytes = bytearray.fromhex(key) - auth_ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) + auth_ret = self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes) if auth_ret.status == chameleon_status.Device.HF_TAG_OK: return key return None @@ -535,8 +566,8 @@ class HFMFRDBL(BaseMF1AuthOpera): # hf mf rdbl -b 2 -t A -k FFFFFFFFFFFF def on_exec(self, args: argparse.Namespace): param = self.get_param(args) - resp = self.cmd.read_mf1_block(param.block, param.type, param.key) - print(f" - Data: {resp.data.hex()}") + resp = self.cmd.mf1_read_one_block(param.block, param.type, param.key) + print(f" - Data: {resp.hex()}") @hf_mf.command('wrbl', 'Mifare Classic write one block') @@ -553,11 +584,11 @@ def on_exec(self, args: argparse.Namespace): if not re.match(r"^[a-fA-F0-9]{32}$", args.data): raise ArgsParserError("Data must include 32 HEX symbols") param.data = bytearray.fromhex(args.data) - resp = self.cmd.write_mf1_block(param.block, param.type, param.key, param.data) - if resp.status == chameleon_status.Device.HF_TAG_OK: - print(f" - {colorama.Fore.GREEN}Write done.{colorama.Style.RESET_ALL}") + resp = self.cmd.mf1_write_one_block(param.block, param.type, param.key, param.data) + if resp: + print(f" - {CG}Write done.{C0}") else: - print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}") + print(f" - {CR}Write fail.{C0}") @hf_mf_detection.command('enable', 'Detection enable') @@ -570,7 +601,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf detection enable -e 1 def on_exec(self, args: argparse.Namespace): enable = True if args.enable == 1 else False - self.cmd.set_mf1_detection_enable(enable) + self.cmd.mf1_set_detection_enable(enable) print(f" - Set mf1 detection {'enable' if enable else 'disable'}.") @@ -581,8 +612,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf detection count def on_exec(self, args: argparse.Namespace): - data_bytes = self.cmd.get_mf1_detection_count().data - count = int.from_bytes(data_bytes, "little", signed=False) + count = self.cmd.mf1_get_detection_count() print(f" - MF1 detection log count = {count}") @@ -599,11 +629,17 @@ def decrypt_by_list(self, rs: list): :param rs: :return: """ - keys = [] + msg1 = f" > {len(rs)} records => " + msg2 = f"/{(len(rs)*(len(rs)-1))//2} combinations. " + msg3 = f" key(s) found" + n = 1 + keys = set() for i in range(len(rs)): item0 = rs[i] for j in range(i + 1, len(rs)): item1 = rs[j] + # TODO: if some keys already recovered, test them on item before running mfkey32 on item + # TODO: if some keys already recovered, remove corresponding items cmd_base = f"{item0['uid']} {item0['nt']} {item0['nr']} {item0['ar']}" cmd_base += f" {item1['nt']} {item1['nr']} {item1['ar']}" if sys.platform == "win32": @@ -621,29 +657,44 @@ def decrypt_by_list(self, rs: list): # print(output_str) sea_obj = re.search(r"([a-fA-F0-9]{12})", output_str, flags=re.MULTILINE) if sea_obj is not None: - keys.append(sea_obj[1]) - + keys.add(sea_obj[1]) + print(f"{msg1}{n}{msg2}{len(keys)}{msg3}\r", end="") + n += 1 + print() return keys # hf mf detection decrypt def on_exec(self, args: argparse.Namespace): - buffer = bytearray() index = 0 - count = int.from_bytes(self.cmd.get_mf1_detection_count().data, "little", signed=False) + count = self.cmd.mf1_get_detection_count() if count == 0: print(" - No detection log to download") return print(f" - MF1 detection log count = {count}, start download", end="") + result_list = [] while index < count: - tmp = self.cmd.get_mf1_detection_log(index).data - recv_count = int(len(tmp) / HFMFDetectionDecrypt.detection_log_size) + tmp = self.cmd.mf1_get_detection_log(index) + recv_count = len(tmp) index += recv_count - buffer.extend(tmp) - print(".", end="") + result_list.extend(tmp) + print("."*recv_count, end="") print() - print(f" - Download done ({len(buffer)}bytes), start parse and decrypt") + print(f" - Download done ({len(result_list)} records), start parse and decrypt") + # classify + result_maps = {} + for item in result_list: + uid = item['uid'] + if uid not in result_maps: + result_maps[uid] = {} + block = item['block'] + if block not in result_maps[uid]: + result_maps[uid][block] = {} + type = item['type'] + if type not in result_maps[uid][block]: + result_maps[uid][block][type] = [] + + result_maps[uid][block][type].append(item) - result_maps = chameleon_cstruct.parse_mf1_detection_result(buffer) for uid in result_maps.keys(): print(f" - Detection log for uid [{uid.upper()}]") result_maps_for_uid = result_maps[uid] @@ -654,11 +705,15 @@ def on_exec(self, args: argparse.Namespace): records = result_maps_for_uid[block]['A'] if len(records) > 1: result_maps[uid][block]['A'] = self.decrypt_by_list(records) + else: + print(f" > {len(records)} record") if 'B' in result_maps_for_uid[block]: # print(f" - B record: { result_maps[block]['B'] }") records = result_maps_for_uid[block]['B'] if len(records) > 1: result_maps[uid][block]['B'] = self.decrypt_by_list(records) + else: + print(f" > {len(records)} record") print(" > Result ---------------------------") for block in result_maps_for_uid.keys(): if 'A' in result_maps_for_uid[block]: @@ -704,14 +759,16 @@ def on_exec(self, args: argparse.Namespace): index = 0 block = 0 - while index < len(buffer): + max_blocks = (self.device_com.data_max_length - 1) // 16 + while index + 16 < len(buffer): # split a block from buffer - block_data = buffer[index: index + 16] - index += 16 + block_data = buffer[index: index + 16*max_blocks] + n_blocks = len(block_data) // 16 + index += 16*n_blocks # load to device - self.cmd.set_mf1_block_data(block, block_data) - print('.', end='') - block += 1 + self.cmd.mf1_write_emu_block_data(block, block_data) + print('.'*n_blocks, end='') + block += n_blocks print("\n - Load success") @@ -735,9 +792,9 @@ def on_exec(self, args: argparse.Namespace): else: content_type = args.type - selected_slot = self.cmd.get_active_slot().data[0] - slot_info = self.cmd.get_slot_info().data - tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot * 2]) + selected_slot = self.cmd.get_active_slot() + slot_info = self.cmd.get_slot_info() + tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot][0]) if tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini: block_count = 20 elif tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024: @@ -749,19 +806,22 @@ def on_exec(self, args: argparse.Namespace): else: raise Exception("Card in current slot is not Mifare Classic/Plus in SL1 mode") - with open(file, 'wb') as fd: - block = 0 - while block < block_count: - response = self.cmd.get_mf1_block_data(block, 1) - print('.', end='') - block += 1 - if content_type == 'hex': - hex_char_repr = binascii.hexlify(response.data) - fd.write(hex_char_repr) - fd.write(bytes([0x0a])) - else: - fd.write(response.data) + index = 0 + data = bytearray(0) + max_blocks = self.device_com.data_max_length // 16 + while block_count > 0: + chunk_count = min(block_count, max_blocks) + data.extend(self.cmd.mf1_read_emu_block_data(index, chunk_count)) + index += chunk_count + block_count -= chunk_count + print('.'*chunk_count, end='') + with open(file, 'wb') as fd: + if content_type == 'hex': + for i in range(len(data) // 16): + fd.write(binascii.hexlify(data[i*16:(i+1)*16])+b'\n') + else: + fd.write(data) print("\n - Read success") @@ -789,16 +849,16 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf settings def on_exec(self, args: argparse.Namespace): if args.gen1a != -1: - self.cmd.set_mf1_gen1a_mode(args.gen1a) + self.cmd.mf1_set_gen1a_mode(args.gen1a) print(f' - Set gen1a mode to {"enabled" if args.gen1a else "disabled"} success') if args.gen2 != -1: - self.cmd.set_mf1_gen2_mode(args.gen2) + self.cmd.mf1_set_gen2_mode(args.gen2) print(f' - Set gen2 mode to {"enabled" if args.gen2 else "disabled"} success') if args.coll != -1: - self.cmd.set_mf1_block_anti_coll_mode(args.coll) + self.cmd.mf1_set_block_anti_coll_mode(args.coll) print(f' - Set anti-collision mode to {"enabled" if args.coll else "disabled"} success') if args.write != -1: - self.cmd.set_mf1_write_mode(args.write) + self.cmd.mf1_set_write_mode(args.write) print(f' - Set write mode to {chameleon_cmd.MifareClassicWriteMode(args.write)} success') print(' - Emulator settings updated') @@ -807,36 +867,44 @@ def on_exec(self, args: argparse.Namespace): class HFMFSim(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") - parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") parser.add_argument('--uid', type=str, required=True, help="Unique ID(hex)", metavar="hex") + parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") + parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") + parser.add_argument('--ats', type=str, required=False, help="Answer To Select(hex)", metavar="hex") return parser # hf mf sim --sak 08 --atqa 0400 --uid DEADBEEF def on_exec(self, args: argparse.Namespace): - sak_str: str = args.sak.strip() - atqa_str: str = args.atqa.strip() uid_str: str = args.uid.strip() - - if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: - sak = bytearray.fromhex(sak_str) + if re.match(r"[a-fA-F0-9]+", uid_str) is not None: + uid = bytes.fromhex(uid_str) + if len(uid) not in [4, 7, 10]: + raise Exception("UID length error") else: - raise Exception("SAK must be hex(2byte)") + raise Exception("UID must be hex") + atqa_str: str = args.atqa.strip() if re.match(r"[a-fA-F0-9]{4}", atqa_str) is not None: - atqa = bytearray.fromhex(atqa_str) + atqa = bytes.fromhex(atqa_str) else: raise Exception("ATQA must be hex(4byte)") - if re.match(r"[a-fA-F0-9]+", uid_str) is not None: - uid_len = len(uid_str) - if uid_len != 8 and uid_len != 14 and uid_len != 20: - raise Exception("UID length error") - uid = bytearray.fromhex(uid_str) + sak_str: str = args.sak.strip() + if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: + sak = bytes.fromhex(sak_str) else: - raise Exception("UID must be hex") + raise Exception("SAK must be hex(2byte)") - self.cmd.set_mf1_anti_collision_res(sak, atqa, uid) + if args.ats is not None: + ats_str: str = args.ats.strip() + if re.match(r"[a-fA-F0-9]+", ats_str) is not None: + ats = bytes.fromhex(ats_str) + else: + raise Exception("ATS must be hex") + else: + ats = b'' + + self.cmd.hf14a_set_anti_coll_data(uid, atqa, sak, ats) print(" - Set anti-collision resources success") @@ -846,17 +914,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def scan(self): - resp: chameleon_com.Response = self.cmd.get_mf1_anti_coll_data() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: - info = chameleon_cstruct.parse_14a_scan_tag_result(resp.data) - print(f"- UID Size: {info['uid_size']}") - print(f"- UID Hex : {info['uid_hex'].upper()}") - print(f"- SAK Hex : {info['sak_hex'].upper()}") - print(f"- ATQA Hex : {info['atqa_hex'].upper()}") - return True - else: - print("No data loaded in slot") - return False + resp = self.cmd.hf14a_get_anti_coll_data() + print(f"- UID : {resp['uid'].hex().upper()}") + print(f"- ATQA : {resp['atqa'].hex().upper()}") + print(f"- SAK : {resp['sak'].hex().upper()}") + if len(resp['ats']) > 0: + print(f"- ATS : {resp['ats'].hex().upper()}") def on_exec(self, args: argparse.Namespace): return self.scan() @@ -868,9 +931,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.read_em_410x() - id_hex = resp.data.hex() - print(f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") + id = self.cmd.em410x_scan() + print(f" - EM410x ID(10H): {CG}{id.hex()}{C0}") class LFEMCardRequiredUnit(DeviceRequiredUnit): @@ -907,8 +969,8 @@ def before_exec(self, args: argparse.Namespace): # lf em write --id 4400999559 def on_exec(self, args: argparse.Namespace): id_hex = args.id - id_bytes = bytearray.fromhex(id_hex) - self.cmd.write_em_410x_to_t55xx(id_bytes) + id_bytes = bytes.fromhex(id_hex) + self.cmd.em410x_write_to_t55xx(id_bytes) print(f" - EM410x ID(10H): {id_hex} write done.") @@ -956,40 +1018,62 @@ class HWSlotList(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('-e', '--extend', type=int, required=False, - help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (" - "default, 2 - show emulator settings for each slot)", choices=[0, 1, 2], default=1) + help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (default)", choices=[0, 1], default=1) return parser def get_slot_name(self, slot, sense): try: - return self.cmd.get_slot_tag_nick_name(slot, sense).data.decode() + name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") + return len(name),len(CC+C0),f'{CC}{name}{C0}' except UnexpectedResponseError: - return "Empty" + return 0,0,'' except UnicodeDecodeError: - return "Non UTF-8" + name = "UTF8 Err" + return len(name),len(CR+C0),f'{CR}{name}{C0}' # hw slot list def on_exec(self, args: argparse.Namespace): - data = self.cmd.get_slot_info().data - selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot().data[0]) - enabled = self.cmd.get_enabled_slots().data + slotinfo = self.cmd.get_slot_info() + selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot()) + enabled = self.cmd.get_enabled_slots() + maxnamelength = 0 + if args.extend: + slotnames = [] + for slot in chameleon_cmd.SlotNumber: + hfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + lfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + m = max(hfn[0], lfn[0]) + maxnamelength = m if m > maxnamelength else maxnamelength + slotnames.append((hfn, lfn)) for slot in chameleon_cmd.SlotNumber: - print(f' - Slot {slot} data{" (active)" if slot == selected else ""}' - f'{" (disabled)" if not enabled[chameleon_cmd.SlotNumber.to_fw(slot)] else ""}:') + fwslot = chameleon_cmd.SlotNumber.to_fw(slot) + hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot][0]) + lf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot][1]) + print(f' - {f"Slot {slot}:":{4+maxnamelength+1}}' + f'{f"({CG}active{C0})" if slot == selected else ""}' + f'{f"({CR}disabled{C0}) " if not enabled[fwslot] else ""}') print(f' HF: ' - f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + " - ") if args.extend else ""}' - f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2])}') + f'{(slotnames[fwslot][0][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][0][1]+1 if args.extend else maxnamelength+1}}' + f'{f"{CY if enabled[fwslot] else C0}{hf_tag_type}{C0}" if hf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') + if args.extend == 1 and \ + enabled[fwslot] and \ + slot == selected and \ + hf_tag_type in [ + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_2048, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_4096, + ]: + config = self.cmd.mf1_get_emulator_config() + print(' - Mifare Classic emulator settings:') + print(f' {"Detection (mfkey32) mode:":40}{f"{CG}enabled{C0}" if config["detection"] else f"{CR}disabled{C0}"}') + print(f' {"Gen1A magic mode:":40}{f"{CG}enabled{C0}" if config["gen1a_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Gen2 magic mode:":40}{f"{CG}enabled{C0}" if config["gen2_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Use anti-collision data from block 0:":40}{f"{CG}enabled{C0}" if config["block_anti_coll_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Write mode:":40}{CY}{chameleon_cmd.MifareClassicWriteMode(config["write_mode"])}{C0}') print(f' LF: ' - f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + " - ") if args.extend else ""}' - f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2 + 1])}') - if args.extend == 2 or args.extend == 1 and enabled[chameleon_cmd.SlotNumber.to_fw(slot)]: - config = self.cmd.get_mf1_emulator_settings().data - print(' - Mifare Classic emulator settings:') - print(f' Detection (mfkey32) mode: {"enabled" if config[0] else "disabled"}') - print(f' Gen1A magic mode: {"enabled" if config[1] else "disabled"}') - print(f' Gen2 magic mode: {"enabled" if config[2] else "disabled"}') - print(f' Use anti-collision data from block 0: {"enabled" if config[3] else "disabled"}') - print(f' Write mode: {chameleon_cmd.MifareClassicWriteMode(config[4])}') + f'{(slotnames[fwslot][1][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][1][1]+1 if args.extend else maxnamelength+1}}' + f'{f"{CY if enabled[fwslot] else C0}{lf_tag_type}{C0}" if lf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') @hw_slot.command('change', 'Set emulation tag slot activated.') @@ -1001,7 +1085,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw slot change -s 1 def on_exec(self, args: argparse.Namespace): slot_index = args.slot - self.cmd.set_slot_activated(slot_index) + self.cmd.set_active_slot(slot_index) print(f" - Set slot {slot_index} activated success.") @@ -1099,8 +1183,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # lf em sim set --id 4545454545 def on_exec(self, args: argparse.Namespace): id_hex = args.id - id_bytes = bytearray.fromhex(id_hex) - self.cmd.set_em410x_sim_id(id_bytes) + self.cmd.em410x_set_emu_id(bytes.fromhex(id_hex)) print(' - Set em410x tag id success.') @@ -1111,9 +1194,9 @@ def args_parser(self) -> ArgumentParserNoExit or None: # lf em sim get def on_exec(self, args: argparse.Namespace): - response = self.cmd.get_em410x_sim_id() + response = self.cmd.em410x_get_emu_id() print(' - Get em410x tag id success.') - print(f'ID: {response.data.hex()}') + print(f'ID: {response.hex()}') @hw_slot_nick.command('set', 'Set tag nick name for slot') @@ -1133,7 +1216,7 @@ def on_exec(self, args: argparse.Namespace): encoded_name = name.encode(encoding="utf8") if len(encoded_name) > 32: raise ValueError("Your tag nick name too long.") - self.cmd.set_slot_tag_nick_name(slot_num, sense_type, encoded_name) + self.cmd.set_slot_tag_nick(slot_num, sense_type, encoded_name) print(f' - Set tag nick name for slot {slot_num} success.') @@ -1149,8 +1232,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): slot_num = args.slot sense_type = args.sense_type - res = self.cmd.get_slot_tag_nick_name(slot_num, sense_type) - print(f' - Get tag nick name for slot {slot_num}: {res.data.decode()}') + res = self.cmd.get_slot_tag_nick(slot_num, sense_type) + print(f' - Get tag nick name for slot {slot_num}: {res.decode(encoding="utf8")}') @hw_slot.command('update', 'Update config & data to device flash') @@ -1160,7 +1243,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw slot update def on_exec(self, args: argparse.Namespace): - self.cmd.update_slot_data_config() + self.cmd.slot_data_config_save() print(' - Update config and data from device memory to flash success.') @@ -1189,7 +1272,7 @@ def on_exec(self, args: argparse.Namespace): print(f' Slot {slot} setting done.') # update config and save to flash - self.cmd.update_slot_data_config() + self.cmd.slot_data_config_save() print(' - Succeeded opening all slots and setting data to default.') @@ -1201,7 +1284,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw dfu def on_exec(self, args: argparse.Namespace): print("Application restarting...") - self.cmd.enter_dfu_mode() + self.cmd.enter_bootloader() # In theory, after the above command is executed, the dfu mode will enter, and then the USB will restart, # To judge whether to enter the USB successfully, we only need to judge whether the USB becomes the VID and PID # of the DFU device. @@ -1218,12 +1301,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp: chameleon_com.Response = self.cmd.get_settings_animation() - if resp.data[0] == 0: + resp = self.cmd.get_animation_mode() + if resp == 0: print("Full animation") - elif resp.data[0] == 1: + elif resp == 1: print("Minimal animation") - elif resp.data[0] == 2: + elif resp == 2: print("No animation") else: print("Unknown setting value, something failed.") @@ -1240,7 +1323,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): mode = args.mode - self.cmd.set_settings_animation(mode) + self.cmd.set_animation_mode(mode) print("Animation mode change success. Do not forget to store your settings in flash!") @@ -1251,8 +1334,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): print("Storing settings...") - resp: chameleon_com.Response = self.cmd.store_settings() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.save_settings(): print(" - Store success @.@~") else: print(" - Store failed") @@ -1265,8 +1347,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): print("Initializing settings...") - resp: chameleon_com.Response = self.cmd.reset_settings() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.reset_settings(): print(" - Reset success @.@~") else: print(" - Reset failed") @@ -1286,8 +1367,7 @@ def on_exec(self, args: argparse.Namespace): if not args.i_know_what_im_doing: print("This time your data's safe. Read the command documentation next time.") return - resp = self.cmd.factory_reset() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.wipe_fds(): print(" - Reset successful! Please reconnect.") # let time for comm thread to close port time.sleep(0.1) @@ -1304,14 +1384,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.battery_information() - voltage = int.from_bytes(resp.data[:2], 'big') - percentage = resp.data[2] + voltage, percentage = self.cmd.get_battery_info() print(" - Battery information:") - print(f" voltage -> {voltage}mV") + print(f" voltage -> {voltage} mV") print(f" percentage -> {percentage}%") if percentage < HWBatteryInfo.BATTERY_LOW_LEVEL: - print(f"{colorama.Fore.RED}[!] Low battery, please charge.{colorama.Style.RESET_ALL}") + print(f"{CR}[!] Low battery, please charge.{C0}") @hw_settings_button_press.command('get', 'Get button press function of Button A and Button B.') @@ -1325,14 +1403,14 @@ def on_exec(self, args: argparse.Namespace): button_list = [chameleon_cmd.ButtonType.ButtonA, chameleon_cmd.ButtonType.ButtonB, ] print("") for button in button_list: - resp = self.cmd.get_button_press_fun(button) - resp_long = self.cmd.get_long_button_press_fun(button) - button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp.data[0]) - button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long.data[0]) - print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}short{colorama.Style.RESET_ALL}:" + resp = self.cmd.get_button_press_config(button) + resp_long = self.cmd.get_long_button_press_config(button) + button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp) + button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long) + print(f" - {CG}{button} {CY}short{C0}:" f" {button_fn}") print(f" usage: {button_fn.usage()}") - print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}long {colorama.Style.RESET_ALL}:" + print(f" - {CG}{button} {CY}long {C0}:" f" {button_long_fn}") print(f" usage: {button_long_fn.usage()}") print("") @@ -1359,9 +1437,9 @@ def on_exec(self, args: argparse.Namespace): button = chameleon_cmd.ButtonType.from_str(args.b) function = chameleon_cmd.ButtonPressFunction.from_int(args.f) if args.long: - self.cmd.set_long_button_press_fun(button, function) + self.cmd.set_long_button_press_config(button, function) else: - self.cmd.set_button_press_fun(button, function) + self.cmd.set_button_press_config(button, function) print(" - Successfully set button function to settings") @@ -1374,23 +1452,23 @@ def args_parser(self) -> ArgumentParserNoExit or None: return parser def on_exec(self, args: argparse.Namespace): - resp = self.cmd.get_ble_connect_key() + resp = self.cmd.get_ble_pairing_key() print(" - The current key of the device(ascii): " - f"{colorama.Fore.GREEN}{resp.data.decode(encoding='ascii')}{colorama.Style.RESET_ALL}") - + f"{CG}{resp.decode(encoding='ascii')}{C0}") + if args.key != None: if len(args.key) != 6: - print(f" - {colorama.Fore.RED}The ble connect key length must be 6{colorama.Style.RESET_ALL}") + print(f" - {CR}The ble connect key length must be 6{C0}") return if re.match(r'[0-9]{6}', args.key): self.cmd.set_ble_connect_key(args.key) print(" - Successfully set ble connect key to :", end='') - print(f"{colorama.Fore.GREEN}" + print(f"{CG}" f" { args.key }" - f"{colorama.Style.RESET_ALL}" + f"{C0}" ) else: - print(f" - {colorama.Fore.RED}Only 6 ASCII characters from 0 to 9 are supported.{colorama.Style.RESET_ALL}") + print(f" - {CR}Only 6 ASCII characters from 0 to 9 are supported.{C0}") @hw_settings.command('blepair', 'Check if BLE pairing is enabled, or set the enable switch for BLE pairing.') @@ -1404,24 +1482,24 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): is_pairing_enable = self.cmd.get_ble_pairing_enable() print(f" - Is ble pairing enable: ", end='') - color = colorama.Fore.GREEN if is_pairing_enable else colorama.Fore.RED + color = CG if is_pairing_enable else CR print( f"{color}" f"{ 'Yes' if is_pairing_enable else 'No' }" - f"{colorama.Style.RESET_ALL}" + f"{C0}" ) if args.enable is not None: if args.enable == 1 and is_pairing_enable: - print(f"{colorama.Fore.YELLOW} It is already in an enabled state.{colorama.Style.RESET_ALL}") + print(f"{CY} It is already in an enabled state.{C0}") return if args.enable == 0 and not is_pairing_enable: - print(f"{colorama.Fore.YELLOW} It is already in a non enabled state.{colorama.Style.RESET_ALL}") + print(f"{CY} It is already in a non enabled state.{C0}") return self.cmd.set_ble_pairing_enable(args.enable) print(f" - Successfully change ble pairing to " - f"{colorama.Fore.GREEN if args.enable else colorama.Fore.RED}" + f"{CG if args.enable else CR}" f"{ 'Enable' if args.enable else 'Disable' } " - f"{colorama.Style.RESET_ALL}" + f"{C0}" "state.") @@ -1449,5 +1527,12 @@ def on_exec(self, args: argparse.Namespace): response = self.cmd.device.send_cmd_sync(args.command, data=bytes.fromhex(args.data), status=0x0) print(" - Received:") print(f" Command: {response.cmd}") - print(f" Status: {response.status}") + status_string = f" Status: {response.status:#02x}" + if response.status in chameleon_status.Device: + status_string += f" {chameleon_status.Device[response.status]}" + if response.status in chameleon_status.message: + status_string += f": {chameleon_status.message[response.status]}" + print(status_string) + else: + print(f" Status: {response.status:#02x}") print(f" Data (HEX): {response.data.hex()}") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 2ea7ffd5..100c4c3b 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -8,9 +8,9 @@ CURRENT_VERSION_SETTINGS = 5 DATA_CMD_GET_APP_VERSION = 1000 -DATA_CMD_CHANGE_MODE = 1001 +DATA_CMD_CHANGE_DEVICE_MODE = 1001 DATA_CMD_GET_DEVICE_MODE = 1002 -DATA_CMD_SET_SLOT_ACTIVATED = 1003 +DATA_CMD_SET_ACTIVE_SLOT = 1003 DATA_CMD_SET_SLOT_TAG_TYPE = 1004 DATA_CMD_SET_SLOT_DATA_DEFAULT = 1005 DATA_CMD_SET_SLOT_ENABLE = 1006 @@ -47,53 +47,57 @@ DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG = 1028 DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG = 1029 -DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG = 1030 -DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG = 1031 - +DATA_CMD_SET_BLE_PAIRING_KEY = 1030 +DATA_CMD_GET_BLE_PAIRING_KEY = 1031 DATA_CMD_DELETE_ALL_BLE_BONDS = 1032 -DATA_CMD_GET_DEVICE = 1033 -DATA_CMD_GET_SETTINGS = 1034 +DATA_CMD_GET_DEVICE_MODEL = 1033 +# FIXME: implemented but unused in CLI commands +DATA_CMD_GET_DEVICE_SETTINGS = 1034 DATA_CMD_GET_DEVICE_CAPABILITIES = 1035 DATA_CMD_GET_BLE_PAIRING_ENABLE = 1036 DATA_CMD_SET_BLE_PAIRING_ENABLE = 1037 -DATA_CMD_SCAN_14A_TAG = 2000 -DATA_CMD_MF1_SUPPORT_DETECT = 2001 -DATA_CMD_MF1_NT_LEVEL_DETECT = 2002 -DATA_CMD_MF1_DARKSIDE_DETECT = 2003 +DATA_CMD_HF14A_SCAN = 2000 +DATA_CMD_MF1_DETECT_SUPPORT = 2001 +DATA_CMD_MF1_DETECT_PRNG = 2002 +# FIXME: implemented but unused in CLI commands +DATA_CMD_MF1_DETECT_DARKSIDE = 2003 DATA_CMD_MF1_DARKSIDE_ACQUIRE = 2004 -DATA_CMD_MF1_NT_DIST_DETECT = 2005 +DATA_CMD_MF1_DETECT_NT_DIST = 2005 DATA_CMD_MF1_NESTED_ACQUIRE = 2006 -DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK = 2007 +DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 -DATA_CMD_SCAN_EM410X_TAG = 3000 -DATA_CMD_WRITE_EM410X_TO_T5577 = 3001 - -DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA = 4000 -DATA_CMD_SET_MF1_ANTI_COLLISION_RES = 4001 - -DATA_CMD_SET_MF1_DETECTION_ENABLE = 4004 -DATA_CMD_GET_MF1_DETECTION_COUNT = 4005 -DATA_CMD_GET_MF1_DETECTION_RESULT = 4006 - -DATA_CMD_READ_MF1_EMU_BLOCK_DATA = 4008 - -DATA_CMD_GET_MF1_EMULATOR_CONFIG = 4009 -DATA_CMD_GET_MF1_GEN1A_MODE = 4010 -DATA_CMD_SET_MF1_GEN1A_MODE = 4011 -DATA_CMD_GET_MF1_GEN2_MODE = 4012 -DATA_CMD_SET_MF1_GEN2_MODE = 4013 -DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL = 4014 -DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL = 4015 -DATA_CMD_GET_MF1_WRITE_MODE = 4016 -DATA_CMD_SET_MF1_WRITE_MODE = 4017 -DATA_CMD_GET_MF1_ANTI_COLL_DATA = 4018 - -DATA_CMD_SET_EM410X_EMU_ID = 5000 -DATA_CMD_GET_EM410X_EMU_ID = 5001 +DATA_CMD_EM410X_SCAN = 3000 +DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 + +DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA = 4000 +DATA_CMD_HF14A_SET_ANTI_COLL_DATA = 4001 +DATA_CMD_MF1_SET_DETECTION_ENABLE = 4004 +DATA_CMD_MF1_GET_DETECTION_COUNT = 4005 +DATA_CMD_MF1_GET_DETECTION_LOG = 4006 +# FIXME: not implemented +DATA_CMD_MF1_GET_DETECTION_ENABLE = 4007 +DATA_CMD_MF1_READ_EMU_BLOCK_DATA = 4008 +DATA_CMD_MF1_GET_EMULATOR_CONFIG = 4009 +# FIXME: not implemented +DATA_CMD_MF1_GET_GEN1A_MODE = 4010 +DATA_CMD_MF1_SET_GEN1A_MODE = 4011 +# FIXME: not implemented +DATA_CMD_MF1_GET_GEN2_MODE = 4012 +DATA_CMD_MF1_SET_GEN2_MODE = 4013 +# FIXME: not implemented +DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE = 4014 +DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE = 4015 +# FIXME: not implemented +DATA_CMD_MF1_GET_WRITE_MODE = 4016 +DATA_CMD_MF1_SET_WRITE_MODE = 4017 +DATA_CMD_HF14A_GET_ANTI_COLL_DATA = 4018 + +DATA_CMD_EM410X_SET_EMU_ID = 5000 +DATA_CMD_EM410X_GET_EMU_ID = 5001 @enum.unique @@ -182,7 +186,7 @@ def __str__(self): return "NTAG 215" elif self == TagSpecificType.TAG_TYPE_NTAG_216: return "NTAG 216" - return "Unknown" + return "Undefined" @enum.unique @@ -212,6 +216,59 @@ def __str__(self): return "None" +@enum.unique +class MifareClassicPrngType(enum.IntEnum): + # the random number of the card response is fixed + STATIC = 0 + # the random number of the card response is weak + WEAK = 1 + # the random number of the card response is unpredictable + HARD = 2 + + @staticmethod + def list(): + return list(map(int, MifareClassicPrngType)) + + def __str__(self): + if self == MifareClassicPrngType.STATIC: + return "Static" + elif self == MifareClassicPrngType.WEAK: + return "Weak" + elif self == MifareClassicPrngType.HARD: + return "Hard" + return "None" + + +@enum.unique +class MifareClassicDarksideStatus(enum.IntEnum): + OK = 0 + # Darkside can't fix NT (PRNG is unpredictable) + CANT_FIX_NT = 1 + # Darkside try to recover a default key + LUCKY_AUTH_OK = 2 + # Darkside can't get tag response enc(nak) + NO_NAK_SENT = 3 + # Darkside running, can't change tag + TAG_CHANGED = 4 + + @staticmethod + def list(): + return list(map(int, MifareClassicDarksideStatus)) + + def __str__(self): + if self == MifareClassicDarksideStatus.OK: + return "Success" + elif self == MifareClassicDarksideStatus.CANT_FIX_NT: + return "Cannot fix NT (unpredictable PRNG)" + elif self == MifareClassicDarksideStatus.LUCKY_AUTH_OK: + return "Try to recover a default key" + elif self == MifareClassicDarksideStatus.NO_NAK_SENT: + return "Cannot get tag response enc(nak)" + elif self == MifareClassicDarksideStatus.TAG_CHANGED: + return "Tag changed during attack" + return "None" + + @enum.unique class ButtonType(enum.IntEnum): # what, you need the doc for button type? maybe chatgpt known... LOL @@ -292,109 +349,160 @@ def __init__(self, chameleon: chameleon_com.ChameleonCom): :param chameleon: chameleon instance, @see chameleon_device.Chameleon """ self.device = chameleon - if not len(self.device.commands): - self.get_device_capabilities() - def get_firmware_version(self) -> int: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_app_version(self): """ Get firmware version number(application) """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION, 0x00) - return int.from_bytes(resp.data, 'little') + resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = struct.unpack('!BB', resp.data) + # older protocol, must upgrade! + if resp.status == 0 and resp.data == b'\x00\x01': + print("Chameleon does not understand new protocol. Please update firmware") + return chameleon_com.Response(cmd=DATA_CMD_GET_APP_VERSION, + status=chameleon_status.Device.STATUS_NOT_IMPLEMENTED) + return resp - def get_device_chip_id(self) -> str: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_chip_id(self): """ Get device chip id """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID, 0x00) - return resp.data.hex() + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.hex() + return resp - def get_device_address(self) -> str: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_address(self): """ Get device address """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS, 0x00) - return resp.data[::-1].hex() + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.hex() + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_git_version(self) -> str: - resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION, 0x00) - return resp.data.decode('utf-8') + resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.decode('utf-8') + return resp + + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_mode(self): + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!?', resp.data) + return resp - def is_reader_device_mode(self) -> bool: + def is_device_reader_mode(self) -> bool: """ Get device mode, reader or tag :return: True is reader mode, else tag mode """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE, 0x00) - return True if resp.data[0] == 1 else False + return self.get_device_mode() # Note: Will return NOT_IMPLEMENTED if one tries to set reader mode on Lite @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_reader_device_mode(self, reader_mode: bool = True): + def change_device_mode(self, mode): + data = struct.pack('!B', mode) + return self.device.send_cmd_sync(DATA_CMD_CHANGE_DEVICE_MODE, data) + + def set_device_reader_mode(self, reader_mode: bool = True): """ Change device mode, reader or tag :param reader_mode: True if reader mode, False if tag mode. :return: """ - return self.device.send_cmd_sync(DATA_CMD_CHANGE_MODE, 0x00, 0x0001 if reader_mode else 0x0000) + self.change_device_mode(reader_mode) @expect_response(chameleon_status.Device.HF_TAG_OK) - def scan_tag_14a(self): + def hf14a_scan(self): """ 14a tags in the scanning field :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_14A_TAG, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_HF14A_SCAN) + if resp.status == chameleon_status.Device.HF_TAG_OK: + # uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + offset = 0 + data = [] + while offset < len(resp.data): + uidlen, = struct.unpack_from('!B', resp.data, offset) + offset += struct.calcsize('!B') + uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset) + offset += struct.calcsize(f'!{uidlen}s2s1sB') + ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset) + offset += struct.calcsize(f'!{atslen}s') + data.append({'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats}) + resp.data = data + return resp - def detect_mf1_support(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_support(self): """ Detect whether it is mifare classic label :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_SUPPORT_DETECT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_SUPPORT) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data, = struct.unpack('!?', resp.data) + return resp - def detect_mf1_nt_level(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_prng(self): """ detect mifare Class of classic nt vulnerabilities :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_LEVEL_DETECT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_PRNG) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = resp.data[0] + return resp - def detect_darkside_support(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_darkside_support(self): """ Check if the card is vulnerable to mifare classic darkside attack :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_DETECT, 0x00, None, timeout=20) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_DARKSIDE, timeout=20) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def detect_nt_distance(self, block_known, type_known, key_known): + def mf1_detect_nt_dist(self, block_known, type_known, key_known): """ Detect the random number distance of the card :return: """ - data = bytearray() - data.append(type_known) - data.append(block_known) - data.extend(key_known) - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_DIST_DETECT, 0x00, data) + data = struct.pack('!BB6s', type_known, block_known, key_known) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_NT_DIST, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + uid, dist = struct.unpack('!II', resp.data) + resp.data = {'uid': uid, 'dist': dist} + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def acquire_nested(self, block_known, type_known, key_known, block_target, type_target): + def mf1_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ Collect the key NT parameters needed for Nested decryption :return: """ - data = bytearray() - data.append(type_known) - data.append(block_known) - data.extend(key_known) - data.append(type_target) - data.append(block_target) - return self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, 0x00, data) + data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = [{'nt': nt, 'nt_enc': nt_enc, 'par': par} + for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)] + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def acquire_darkside(self, block_target, type_target, first_recover: int or bool, sync_max): + def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or bool, sync_max): """ Collect the key parameters needed for Darkside decryption :param block_target: @@ -403,17 +511,18 @@ def acquire_darkside(self, block_target, type_target, first_recover: int or bool :param sync_max: :return: """ - data = bytearray() - data.append(type_target) - data.append(block_target) - if isinstance(first_recover, bool): - first_recover = 0x01 if first_recover else 0x00 - data.append(first_recover) - data.append(sync_max) - return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, 0x00, data, timeout=sync_max + 5) + data = struct.pack('!BBBB', type_target, block_target, first_recover, sync_max) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, data, timeout=sync_max + 5) + if resp.status == chameleon_status.Device.HF_TAG_OK: + if resp.data[0] == MifareClassicDarksideStatus.OK: + darkside_status, uid, nt1, par, ks1, nr, ar = struct.unpack('!BIIIIII', resp.data) + resp.data = (darkside_status, {'uid': uid, 'nt1': nt1, 'par': par, 'ks1': ks1, 'nr': nr, 'ar': ar}) + else: + resp.data = (resp.data[0],) + return resp @expect_response([chameleon_status.Device.HF_TAG_OK, chameleon_status.Device.MF_ERR_AUTH]) - def auth_mf1_key(self, block, type_value, key): + def mf1_auth_one_key_block(self, block, type_value, key): """ Verify the mf1 key, only verify the specified type of key for a single sector :param block: @@ -421,14 +530,13 @@ def auth_mf1_key(self, block, type_value, key): :param key: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, 0x00, data) + data = struct.pack('!BB6s', type_value, block, key) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, data) + resp.data = resp.status == chameleon_status.Device.HF_TAG_OK + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def read_mf1_block(self, block, type_value, key): + def mf1_read_one_block(self, block, type_value, key): """ read one mf1 block :param block: @@ -436,14 +544,11 @@ def read_mf1_block(self, block, type_value, key): :param key: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, 0x00, data) + data = struct.pack('!BB6s', type_value, block, key) + return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, data) @expect_response(chameleon_status.Device.HF_TAG_OK) - def write_mf1_block(self, block, type_value, key, block_data): + def mf1_write_one_block(self, block, type_value, key, block_data): """ Write mf1 single block :param block: @@ -452,64 +557,66 @@ def write_mf1_block(self, block, type_value, key, block_data): :param block_data: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - data.extend(block_data) - return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, 0x00, data) + data = struct.pack('!BB6s16s', type_value, block, key, block_data) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, data) + resp.data = resp.status == chameleon_status.Device.HF_TAG_OK + return resp @expect_response(chameleon_status.Device.LF_TAG_OK) - def read_em_410x(self): + def em410x_scan(self): """ Read the card number of EM410X :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_EM410X_TAG, 0x00) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SCAN) @expect_response(chameleon_status.Device.LF_TAG_OK) - def write_em_410x_to_t55xx(self, id_bytes: bytearray): + def em410x_write_to_t55xx(self, id_bytes: bytes): """ Write EM410X card number into T55XX :param id_bytes: ID card number :return: """ - new_key = [0x20, 0x20, 0x66, 0x66] - old_keys = [[0x51, 0x24, 0x36, 0x48], [0x19, 0x92, 0x04, 0x27]] + new_key = b'\x20\x20\x66\x66' + old_keys = [b'\x51\x24\x36\x48', b'\x19\x92\x04\x27'] if len(id_bytes) != 5: raise ValueError("The id bytes length must equal 5") - data = bytearray() - data.extend(id_bytes) - data.extend(new_key) - for key in old_keys: - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_WRITE_EM410X_TO_T5577, 0x00, data) + data = struct.pack(f'!5s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys)) + return self.device.send_cmd_sync(DATA_CMD_EM410X_WRITE_TO_T55XX, data) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_slot_info(self): """ Get slots info :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [struct.unpack_from('!HH', resp.data, i) + for i in range(0, len(resp.data), struct.calcsize('!HH'))] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_active_slot(self): """ Get selected slot :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_activated(self, slot_index: SlotNumber): + def set_active_slot(self, slot_index: SlotNumber): """ Set the card slot currently active for use :param slot_index: Card slot index :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ACTIVATED, 0x00, data) + data = struct.pack('!B', SlotNumber.to_fw(slot_index)) + return self.device.send_cmd_sync(DATA_CMD_SET_ACTIVE_SLOT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): @@ -522,10 +629,8 @@ def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(tag_type) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, 0x00, data) + data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseType): @@ -535,8 +640,8 @@ def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseTyp :param sense_type: Sense type to disable :return: """ - return self.device.send_cmd_sync(DATA_CMD_DELETE_SLOT_SENSE_TYPE, 0x00, - bytearray([SlotNumber.to_fw(slot_index), sense_type])) + data = struct.pack('!BB', SlotNumber.to_fw(slot_index), sense_type) + return self.device.send_cmd_sync(DATA_CMD_DELETE_SLOT_SENSE_TYPE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType): @@ -548,13 +653,11 @@ def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificTyp :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(tag_type) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, 0x00, data) + data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_enable(self, slot_index: SlotNumber, enable: bool): + def set_slot_enable(self, slot_index: SlotNumber, enabled: bool): """ Set whether the specified card slot is enabled :param slot_index: Card slot number @@ -562,59 +665,79 @@ def set_slot_enable(self, slot_index: SlotNumber, enable: bool): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(0x01 if enable else 0x00) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, 0X00, data) + data = struct.pack('!BB', SlotNumber.to_fw(slot_index), enabled) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_em410x_sim_id(self, id_bytes: bytearray): + def em410x_set_emu_id(self, id: bytes): """ Set the card number simulated by EM410x :param id_bytes: byte of the card number :return: """ - if len(id_bytes) != 5: + if len(id) != 5: raise ValueError("The id bytes length must equal 5") - return self.device.send_cmd_sync(DATA_CMD_SET_EM410X_EMU_ID, 0x00, id_bytes) + data = struct.pack('5s', id) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SET_EMU_ID, data) - def get_em410x_sim_id(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def em410x_get_emu_id(self): """ Get the simulated EM410x card id """ - return self.device.send_cmd_sync(DATA_CMD_GET_EM410X_EMU_ID, 0x00) + return self.device.send_cmd_sync(DATA_CMD_EM410X_GET_EMU_ID) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_detection_enable(self, enable: bool): + def mf1_set_detection_enable(self, enabled: bool): """ Set whether to enable the detection of the current card slot :param enable: Whether to enable :return: """ - data = bytearray() - data.append(0x01 if enable else 0x00) - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_DETECTION_ENABLE, 0x00, data) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_DETECTION_ENABLE, data) - def get_mf1_detection_count(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_get_detection_count(self): """ Get the statistics of the current detection records :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_COUNT) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!I', resp.data) + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_mf1_detection_log(self, index: int): + def mf1_get_detection_log(self, index: int): """ Get detection logs from the specified index position :param index: start index :return: """ - data = bytearray() - data.extend(index.to_bytes(4, "big", signed=False)) - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_RESULT, 0x00, data) + data = struct.pack('!I', index) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_LOG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + # convert + result_list = [] + pos = 0 + while pos < len(resp.data): + block, bitfield, uid, nt, nr, ar = struct.unpack_from('!BB4s4s4s4s', resp.data, pos) + result_list.append({ + 'block': block, + 'type': ['A', 'B'][bitfield & 0x01], + 'is_nested': bool(bitfield & 0x02), + 'uid': uid.hex(), + 'nt': nt.hex(), + 'nr': nr.hex(), + 'ar': ar.hex() + }) + pos += struct.calcsize('!BB4s4s4s4s') + resp.data = result_list + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_block_data(self, block_start: int, block_data: bytearray): + def mf1_write_emu_block_data(self, block_start: int, block_data: bytes): """ Set the block data of the analog card of MF1 :param block_start: Start setting the location of block data, including this location @@ -622,61 +745,57 @@ def set_mf1_block_data(self, block_start: int, block_data: bytearray): can contain multiple block data, automatically from block_start increment :return: """ - data = bytearray() - data.append(block_start & 0xFF) - data.extend(block_data) - return self.device.send_cmd_sync(DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA, 0x00, data) + data = struct.pack(f'!B{len(block_data)}s', block_start, block_data) + return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, data) - def get_mf1_block_data(self, block_start: int, block_count: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_read_emu_block_data(self, block_start: int, block_count: int): """ Gets data for selected block range """ - return self.device.send_cmd_sync(DATA_CMD_READ_MF1_EMU_BLOCK_DATA, 0x00, bytearray([block_start, block_count])) + data = struct.pack('!BB', block_start, block_count) + return self.device.send_cmd_sync(DATA_CMD_MF1_READ_EMU_BLOCK_DATA, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray): + def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ - Set the anti-collision resource information of the MF1 analog card + Set anti-collision data of current HF slot (UID/SAK/ATQA/ATS) + :param uid: uid bytes + :param atqa: atqa bytes :param sak: sak bytes - :param atqa: atqa array - :param uid: card number array + :param ats: ats bytes (optional) :return: """ - data = bytearray() - data.extend(sak) - data.extend(atqa) - data.extend(uid) - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_ANTI_COLLISION_RES, 0X00, data) + data = struct.pack(f'!B{len(uid)}s2s1sB{len(ats)}s', len(uid), uid, atqa, sak, len(ats), ats) + return self.device.send_cmd_sync(DATA_CMD_HF14A_SET_ANTI_COLL_DATA, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): + def set_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): """ - Set the anti-collision resource information of the MF1 analog card + Set the nick name of the slot :param slot: Card slot number :param sense_type: field type :param name: Card slot nickname :return: """ # SlotNumber() will raise error for us if slot not in slot range - data = bytearray() - 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) + data = struct.pack(f'!BB{len(name)}s', SlotNumber.to_fw(slot), sense_type, name) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType): + def get_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType): """ - Set the anti-collision resource information of the MF1 analog card + Get the nick name of the slot :param slot: Card slot number :param sense_type: field type :return: """ # SlotNumber() will raise error for us if slot not in slot range - data = bytearray() - data.extend([SlotNumber.to_fw(slot), sense_type]) - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, 0x00, data) + data = struct.pack('!BB', SlotNumber.to_fw(slot), sense_type) + return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, data) - def get_mf1_emulator_settings(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_get_emulator_config(self): """ Get array of Mifare Classic emulators settings: [0] - mf1_is_detection_enable (mfkey32) @@ -686,118 +805,163 @@ def get_mf1_emulator_settings(self): [4] - mf1_get_write_mode :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_EMULATOR_CONFIG, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_EMULATOR_CONFIG) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + b1, b2, b3, b4, b5 = struct.unpack('!????B', resp.data) + resp.data = {'detection': b1, + 'gen1a_mode': b2, + 'gen2_mode': b3, + 'block_anti_coll_mode': b4, + 'write_mode': b5} + return resp - def set_mf1_gen1a_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_gen1a_mode(self, enabled: bool): """ Set gen1a magic mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN1A_MODE, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_GEN1A_MODE, data) - def set_mf1_gen2_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_gen2_mode(self, enabled: bool): """ Set gen2 magic mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN2_MODE, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_GEN2_MODE, data) - def set_mf1_block_anti_coll_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_block_anti_coll_mode(self, enabled: bool): """ Set 0 block anti-collision data """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, data) - def set_mf1_write_mode(self, mode: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_write_mode(self, mode: int): """ Set write mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_WRITE_MODE, 0x00, bytearray([mode])) + data = struct.pack('!B', mode) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_WRITE_MODE, data) - def update_slot_data_config(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def slot_data_config_save(self): """ Update the configuration and data of the card slot to flash. :return: """ - return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE, 0x00) + return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE) - def enter_dfu_mode(self): + def enter_bootloader(self): """ Reboot into DFU mode (bootloader) :return: """ - return self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, 0x00, close=True) + self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, close=True) - def get_settings_animation(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_animation_mode(self): """ Get animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_enabled_slots(self): """ Get enabled slots """ - return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS, 0x00) + return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS) - def set_settings_animation(self, value: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def set_animation_mode(self, value: int): """ Set animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, 0x00, bytearray([value])) + data = struct.pack('!B', value) + return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, data) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def reset_settings(self): """ Reset settings stored in flash memory """ - return self.device.send_cmd_sync(DATA_CMD_RESET_SETTINGS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_RESET_SETTINGS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS + return resp - def store_settings(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def save_settings(self): """ Store settings to flash memory """ - return self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS + return resp - def factory_reset(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def wipe_fds(self): """ Reset to factory settings """ - ret = self.device.send_cmd_sync(DATA_CMD_WIPE_FDS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_WIPE_FDS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS self.device.close() - return ret + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def battery_information(self): + def get_battery_info(self): """ Get battery info """ - return self.device.send_cmd_sync(DATA_CMD_GET_BATTERY_INFO, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BATTERY_INFO) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = struct.unpack('!HB', resp.data) + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_button_press_fun(self, button: ButtonType): + def get_button_press_config(self, button: ButtonType): """ Get config of button press function """ - return self.device.send_cmd_sync(DATA_CMD_GET_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + data = struct.pack('!B', button) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BUTTON_PRESS_CONFIG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + def set_button_press_config(self, button: ButtonType, function: ButtonPressFunction): """ Set config of button press function """ - return self.device.send_cmd_sync(DATA_CMD_SET_BUTTON_PRESS_CONFIG, 0x00, bytearray([button, function])) + data = struct.pack('!BB', button, function) + return self.device.send_cmd_sync(DATA_CMD_SET_BUTTON_PRESS_CONFIG, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_long_button_press_fun(self, button: ButtonType): + def get_long_button_press_config(self, button: ButtonType): """ - Get config of button press function + Get config of long button press function """ - return self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + data = struct.pack('!B', button) + resp = self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_long_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + def set_long_button_press_config(self, button: ButtonType, function: ButtonPressFunction): """ - Set config of button press function + Set config of long button press function """ - return self.device.send_cmd_sync(DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button, function])) + data = struct.pack('!BB', button, function) + return self.device.send_cmd_sync(DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_ble_connect_key(self, key: str): @@ -810,44 +974,53 @@ def set_ble_connect_key(self, key: str): if len(data_bytes) != 6: raise ValueError("The ble connect key length must be 6") - return self.device.send_cmd_sync(DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG, 0x00, data_bytes) + data = struct.pack('6s', data_bytes) + return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_KEY, data) - def get_ble_connect_key(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_ble_pairing_key(self): """ Get config of ble connect key """ - return self.device.send_cmd_sync(DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_KEY) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_ble_all_bonds(self): """ From peer manager delete all bonds. """ - return self.device.send_cmd_sync(DATA_CMD_DELETE_ALL_BLE_BONDS, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_DELETE_ALL_BLE_BONDS) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_capabilities(self): """ - Get (and set) commands that client understands + Get list of commands that client understands """ - - commands = [] - try: - ret = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES, 0x00) - self.device.commands = struct.unpack(f"{len(ret.data) // 2}H", ret.data) - except: - print("Chameleon doesn't understand get capabilities command. Please update firmware") - - return commands + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES) + except chameleon_com.CMDInvalidException: + print("Chameleon does not understand get_device_capabilities command. Please update firmware") + return chameleon_com.Response(cmd=DATA_CMD_GET_DEVICE_CAPABILITIES, + status=chameleon_status.Device.STATUS_NOT_IMPLEMENTED) + else: + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [x[0] for x in struct.iter_unpack('!H', resp.data)] + return resp - def get_device_type(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_model(self): """ - Get device type + Get device model 0 - Chameleon Ultra 1 - Chameleon Lite """ - return self.device.send_cmd_sync(DATA_CMD_GET_DEVICE, 0x00).data[0] + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODEL) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_settings(self): """ Get all possible settings @@ -858,33 +1031,63 @@ def get_device_settings(self): settings[3] = settings_get_button_press_config('B'); // short B button press mode settings[4] = settings_get_long_button_press_config('A'); // long A button press mode settings[5] = settings_get_long_button_press_config('B'); // long B button press mode - settings[6] = settings_get_ble_pairing_enable(); // is device require pairing - settings[7:13] = settings_get_ble_connect_key(); // BLE connection key - """ - data = self.device.send_cmd_sync(DATA_CMD_GET_SETTINGS, 0x00).data - if data[0] != CURRENT_VERSION_SETTINGS: - raise ValueError("Settings version in app doesn't match Chameleon response. Upgrade client or Chameleon " - "firmware") - return data + settings[6] = settings_get_ble_pairing_enable(); // does device require pairing + settings[7:13] = settings_get_ble_pairing_key(); // BLE pairing key + """ + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_SETTINGS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if resp.data[0] > CURRENT_VERSION_SETTINGS: + raise ValueError("Settings version in app older than Chameleon. " + "Please upgrade client") + if resp.data[0] < CURRENT_VERSION_SETTINGS: + raise ValueError("Settings version in app newer than Chameleon. " + "Please upgrade Chameleon firmware") + settings_version, animation_mode, btn_press_A, btn_press_B, btn_long_press_A, btn_long_press_B, ble_pairing_enable, ble_pairing_key = struct.unpack( + '!BBBBBBB6s', resp.data) + resp.data = {'settings_version': settings_version, + 'animation_mode': animation_mode, + 'btn_press_A': btn_press_A, + 'btn_press_B': btn_press_B, + 'btn_long_press_A': btn_long_press_A, + 'btn_long_press_B': btn_long_press_B, + 'ble_pairing_enable': ble_pairing_enable, + 'ble_pairing_key': ble_pairing_key} + return resp - def get_mf1_anti_coll_data(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def hf14a_get_anti_coll_data(self): """ - Get data from current slot (UID/SAK/ATQA) + Get anti-collision data from current HF slot (UID/SAK/ATQA/ATS) :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_ANTI_COLL_DATA, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_HF14A_GET_ANTI_COLL_DATA) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS and len(resp.data) > 0: + # uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + offset = 0 + uidlen, = struct.unpack_from('!B', resp.data, offset) + offset += struct.calcsize('!B') + uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset) + offset += struct.calcsize(f'!{uidlen}s2s1sB') + ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset) + offset += struct.calcsize(f'!{atslen}s') + resp.data = {'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats} + return resp - def get_ble_pairing_enable(self) -> bool: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_ble_pairing_enable(self): """ Is ble pairing enable? :return: True if pairing is enable, False if pairing disabled """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE, 0x00) - return resp.data[0] == 1 - + resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!?', resp.data) + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_ble_pairing_enable(self, enable: bool): - return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, 0x00, 1 if enable else 0) + def set_ble_pairing_enable(self, enabled: bool): + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, data) if __name__ == '__main__': @@ -892,8 +1095,8 @@ def set_ble_pairing_enable(self, enable: bool): dev = chameleon_com.ChameleonCom() dev.open("com19") cml = ChameleonCMD(dev) - ver = cml.get_firmware_version() - print(f"Firmware number of application: {ver}") + ver = cml.get_app_version() + print(f"Firmware number of application: {ver[0]}.{ver[1]}") chip = cml.get_device_chip_id() print(f"Device chip id: {chip}") diff --git a/software/script/chameleon_com.py b/software/script/chameleon_com.py index c309fb1f..023f17e2 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -29,7 +29,7 @@ class Response: Chameleon Response Data """ - def __init__(self, cmd, status, data): + def __init__(self, cmd, status, data=b''): self.cmd = cmd self.status = status self.data: bytearray = data @@ -153,37 +153,35 @@ def thread_data_receive(self): if len(data_bytes) > 0: data_byte = data_bytes[0] data_buffer.append(data_byte) - if data_position < 2: # start of frame + if data_position < struct.calcsize('!BB'): # start of frame + lrc1 if data_position == 0: if data_buffer[data_position] != self.data_frame_sof: print("Data frame no sof byte.") data_position = 0 data_buffer.clear() continue - if data_position == 1: - if data_buffer[data_position] != self.lrc_calc(data_buffer[0:1]): + if data_position == struct.calcsize('!B'): + if data_buffer[data_position] != self.lrc_calc(data_buffer[:data_position]): data_position = 0 data_buffer.clear() print("Data frame sof lrc error.") continue - elif data_position == 8: # frame head lrc - if data_buffer[data_position] != self.lrc_calc(data_buffer[0:8]): + elif data_position == struct.calcsize('!BBHHH'): # frame head lrc + if data_buffer[data_position] != self.lrc_calc(data_buffer[:data_position]): data_position = 0 data_buffer.clear() print("Data frame head lrc error.") continue # frame head complete, cache info - data_cmd = struct.unpack(">H", data_buffer[2:4])[0] - data_status = struct.unpack(">H", data_buffer[4:6])[0] - data_length = struct.unpack(">H", data_buffer[6:8])[0] + _, _, data_cmd, data_status, data_length = struct.unpack("!BBHHH", data_buffer[:data_position]) if data_length > self.data_max_length: data_position = 0 data_buffer.clear() - print("Data frame data length too than of max.") + print("Data frame data length larger than max.") continue - elif data_position > 8: # // frame data - if data_position == (8 + data_length + 1): - if data_buffer[data_position] == self.lrc_calc(data_buffer[0:-1]): + elif data_position > struct.calcsize('!BBHHH'): # // frame data + if data_position == (struct.calcsize(f'!BBHHHB{data_length}s')): + if data_buffer[data_position] == self.lrc_calc(data_buffer[:data_position]): # ok, lrc for data is correct. # and we are receive completed # print(f"Buffer data = {data_buffer.hex()}") @@ -193,7 +191,8 @@ def thread_data_receive(self): fn_call = self.wait_response_map[data_cmd]['callback'] else: fn_call = None - data_response = data_buffer[9: 9 + data_length] + data_response = data_buffer[struct.calcsize('!BBHHHB'): + struct.calcsize(f'!BBHHHB{data_length}s')] if callable(fn_call): # delete wait task from map del self.wait_response_map[data_cmd] @@ -204,7 +203,7 @@ def thread_data_receive(self): else: print(f"No task wait process: ${data_cmd}") else: - print("Data frame finally lrc error.") + print("Data frame global lrc error.") data_position = 0 data_buffer.clear() continue @@ -265,36 +264,32 @@ def thread_check_timeout(self): self.wait_response_map[task_cmd]['is_timeout'] = True time.sleep(0.001) - def make_data_frame_bytes(self, cmd: int, status: int, data: bytearray = None) -> bytearray: + def make_data_frame_bytes(self, cmd: int, data: bytearray = None, status: int = 0) -> bytearray: """ Make data frame :return: frame """ - frame = bytearray() - # sof and sof lrc byte - frame.append(self.data_frame_sof) - frame.append(self.lrc_calc(frame[0:1])) - # head info - frame.extend(struct.pack('>H', cmd)) - frame.extend(struct.pack('>H', status)) - frame.extend(struct.pack('>H', len(data) if data is not None else 0)) - frame.append(self.lrc_calc(frame[2:8])) - # data - if data is not None: - frame.extend(data) - # frame lrc - frame.append(self.lrc_calc(frame)) + if data is None: + data = b'' + frame = bytearray(struct.pack(f'!BBHHHB{len(data)}sB', + self.data_frame_sof, 0x00, cmd, status, len(data), 0x00, data, 0x00)) + # lrc1 + frame[struct.calcsize('!B')] = self.lrc_calc(frame[:struct.calcsize('!B')]) + # lrc2 + frame[struct.calcsize('!BBHHH')] = self.lrc_calc(frame[:struct.calcsize('!BBHHH')]) + # lrc3 + frame[struct.calcsize(f'!BBHHHB{len(data)}s')] = self.lrc_calc(frame[:struct.calcsize(f'!BBHHHB{len(data)}s')]) return frame - def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback=None, timeout: int = 3, + def send_cmd_auto(self, cmd: int, data: bytearray = None, status: int = 0, callback=None, timeout: int = 3, close: bool = False): """ Send cmd to device - :param timeout: wait response timeout :param cmd: cmd - :param status: status(optional) + :param data: bytes data (optional) + :param status: status (optional) :param callback: call on response - :param data: bytes data + :param timeout: wait response timeout :param close: close connection after executing :return: """ @@ -303,20 +298,20 @@ def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback= if cmd in self.wait_response_map: del self.wait_response_map[cmd] # make data frame - data_frame = self.make_data_frame_bytes(cmd, status, data) + data_frame = self.make_data_frame_bytes(cmd, data, status) task = {'cmd': cmd, 'frame': data_frame, 'timeout': timeout, 'close': close} if callable(callback): task['callback'] = callback self.send_data_queue.put(task) return self - def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list or int = None, + def send_cmd_sync(self, cmd: int, data: bytearray or bytes or list or int = None, status: int = 0, timeout: int = 3) -> Response: """ Send cmd to device, and block receive data. :param cmd: cmd - :param status: status(optional) - :param data: bytes data + :param data: bytes data (optional) + :param status: status (optional) :param timeout: wait response timeout :return: response data """ @@ -327,9 +322,8 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list if cmd not in self.commands: raise CMDInvalidException(f"This device doesn't declare that it can support this command: {cmd}.\nMake " f"sure firmware is up to date and matches client") - # return Response(cmd=cmd, status=0, data=b"\0" * 32) # forge fake response to not break app # first to send cmd, no callback mode(sync) - self.send_cmd_auto(cmd, status, data, None, timeout) + self.send_cmd_auto(cmd, data, status, None, timeout) # wait cmd start process while cmd not in self.wait_response_map: time.sleep(0.01) @@ -348,5 +342,5 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list if __name__ == '__main__': cml = ChameleonCom().open("com19") - resp = cml.send_cmd_sync(0x03E9, 0xBEEF, bytearray([0x01, 0x02])) + resp = cml.send_cmd_sync(0x03E9, bytearray([0x01, 0x02]), 0xBEEF) print(resp) diff --git a/software/script/chameleon_cstruct.py b/software/script/chameleon_cstruct.py deleted file mode 100644 index 14edc5fe..00000000 --- a/software/script/chameleon_cstruct.py +++ /dev/null @@ -1,113 +0,0 @@ -""" - From bytes to c struct parser(Chameleon data response) -""" - - -def bytes_to_u32(byte_array): - """ - bytes array to u32 int - :param byte_array: - :return: - """ - return int.from_bytes(byte_array, byteorder='big', signed=False) - - -def parse_14a_scan_tag_result(data: bytearray): - """ - From bytes parse tag info - :param data: - :return: - """ - return { - 'uid_size': data[10], - 'uid_hex': data[0:data[10]].hex(), - 'sak_hex': hex(data[12]).lstrip('0x').rjust(2, '0'), - 'atqa_hex': data[13:15].hex().upper() - } - - -def parse_nt_distance_detect_result(data: bytearray): - """ - From bytes parse nt distance - :param data: data - :return: - """ - return {'uid': bytes_to_u32(data[0:4]), 'dist': bytes_to_u32(data[4:8])} - - -def parse_nested_nt_acquire_group(data: bytearray): - """ - From bytes parse nested param - :param data: data - :return: - """ - group = [] - if len(data) % 9 != 0: - raise ValueError( - "Nt data length error, except: { nt(4byte), nt_enc(4byte), par(1byte) } * N") - i = 0 - while i < len(data): - group.append({ - 'nt': bytes_to_u32(data[i: i + 4]), - 'nt_enc': bytes_to_u32(data[i + 4: i + 8]), - 'par': data[i + 8] - }) - i += 9 - return group - - -def parse_darkside_acquire_result(data: bytearray): - """ - From bytes parse darkside param - :param data: data - :return: - """ - return { - 'uid': bytes_to_u32(data[0: 4]), - 'nt1': bytes_to_u32(data[4: 8]), - 'par': bytes_to_u32(data[8: 16]), - 'ks1': bytes_to_u32(data[16: 24]), - 'nr': bytes_to_u32(data[24: 28]), - 'ar': bytes_to_u32(data[28: 32]) - } - - -def parse_mf1_detection_result(data: bytearray): - """ - From bytes parse detection param - :param data: data - :return: - """ - # convert - result_list = [] - pos = 0 - while pos < len(data): - result_list.append({ - 'block': data[0 + pos], - 'type': 0x60 + (data[1 + pos] & 0x01), - 'is_nested': True if data[1 + pos] >> 1 & 0x01 == 0x01 else False, - 'uid': data[2 + pos: 6 + pos].hex(), - 'nt': data[6 + pos: 10 + pos].hex(), - 'nr': data[10 + pos: 14 + pos].hex(), - 'ar': data[14 + pos: 18 + pos].hex() - }) - pos += 18 - - # classify - result_map = {} - for item in result_list: - uid = item['uid'] - if uid not in result_map: - result_map[uid] = {} - - block = item['block'] - if block not in result_map[uid]: - result_map[uid][block] = {} - - type_chr = 'A' if item['type'] == 0x60 else 'B' - if type_chr not in result_map[uid][block]: - result_map[uid][block][type_chr] = [] - - result_map[uid][block][type_chr].append(item) - - return result_map diff --git a/software/script/chameleon_status.py b/software/script/chameleon_status.py index c3a9180b..cf820579 100644 --- a/software/script/chameleon_status.py +++ b/software/script/chameleon_status.py @@ -12,6 +12,14 @@ def __contains__(self, item): return True return False + def __getitem__(self, item): + for field in self.__dict__: + val = self.__dict__[field] + if isinstance(val, int): + if val == item: + return field + return False + class Device(metaclass=MetaDevice): HF_TAG_OK = 0x00 # IC card operation is successful @@ -23,20 +31,6 @@ class Device(metaclass=MetaDevice): MF_ERR_AUTH = 0x06 # MF card verification failed HF_ERR_PARITY = 0x07 # IC card parity error - # Darkside, the random number cannot be fixed, this situation may appear on the UID card - DARKSIDE_CANT_FIXED_NT = 0x20 - # Darkside, the direct verification is successful, maybe the key is empty - DARKSIDE_LUCK_AUTH_OK = 0x21 - # Darkside, the card doesn't respond to nack, probably a card that fixes the nack logic bug - DARKSIDE_NACK_NO_SEND = 0x22 - # Darkside, there is a card switching during the running of darkside, - # maybe there is a signal problem, or the two cards really switched quickly - DARKSIDE_TAG_CHANGED = 0x23 - # Nested, it is detected that the random number of the card response is fixed - NESTED_TAG_IS_STATIC = 0x24 - # Nested, detected nonce for card response is unpredictable - NESTED_TAG_IS_HARD = 0x25 - # Some operations with low frequency cards succeeded! LF_TAG_OK = 0x40 # Unable to search for a valid EM410X label @@ -64,13 +58,6 @@ class Device(metaclass=MetaDevice): Device.MF_ERR_AUTH: "HF tag auth fail", Device.HF_ERR_PARITY: "HF tag data parity error", - Device.DARKSIDE_CANT_FIXED_NT: "Darkside Can't select a nt(PRNG is unpredictable)", - Device.DARKSIDE_LUCK_AUTH_OK: "Darkside try to recover a default key", - Device.DARKSIDE_NACK_NO_SEND: "Darkside can't make tag response nack(enc)", - Device.DARKSIDE_TAG_CHANGED: "Darkside running, can't change tag", - Device.NESTED_TAG_IS_STATIC: "StaticNested tag, not weak nested", - Device.NESTED_TAG_IS_HARD: "HardNested tag, not weak nested", - Device.LF_TAG_OK: "LF tag operation succeeded", Device.EM410X_TAG_NO_FOUND: "EM410x tag no found", diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index d2e8e85c..bca09b40 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -54,7 +54,6 @@ 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( @@ -62,8 +61,8 @@ def error_throwing_func(*args, **kwargs): else: raise UnexpectedResponseError( f"Unexpected response and unknown status {ret.status}") - - return ret + + return ret.data return error_throwing_func