From b61657e3314de0f68240ed05a5a213b82c1e4366 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 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. - 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 - not breaking but many commands have been renamed for consistency. you are invited to adapt your client for easier maintenance Other changes: - FW/CLI: changes to match the aforementioned guidelines - CLI: does not instanciate ChameleonCMD on every single command - CLI: query capabilities on connect, not on every single command if device does not support the get_device_capabilities command - CLI: hw raw: detail status messages - CLI: merge https://github.com/RfidResearchGroup/ChameleonUltra/pull/143 manually - CLI: HF14AInfo logic moved inside HF14AScan - FW: replace dynamic cmd_map_init() by static cmd_map initialization - probably many little ones 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 - continue cmd list review, cf protocol.md - document cmd in protocol.md - apply recommendations given in "New data payloads: guidelines for developers" - use @expect_response_ng, temporary name as it should replace @expect_response once done everywhere - rewrite cmd pack if needed - rewrite cmd unpack if needed - rewrite resp pack if needed, replace num_to_bytes - rewrite resp unpack if needed, limit data parsing to cmd handlers, revise chameleon_cstruct - TEST! --- CHANGELOG.md | 1 + 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 | 260 ++++++- firmware/application/src/app_cmd.c | 645 ++++++++++------- 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 | 4 +- firmware/application/src/data_cmd.h | 71 +- .../application/src/rfid/nfctag/hf/nfc_mf1.c | 2 +- .../application/src/rfid/nfctag/hf/nfc_mf1.h | 2 +- .../src/rfid/reader/hf/mf1_toolbox.c | 117 ++-- .../src/rfid/reader/hf/mf1_toolbox.h | 40 +- .../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 | 243 ++++--- software/script/chameleon_cmd.py | 651 +++++++++++------- software/script/chameleon_com.py | 73 +- software/script/chameleon_cstruct.py | 63 +- software/script/chameleon_status.py | 29 +- software/script/chameleon_utils.py | 28 + 30 files changed, 1548 insertions(+), 1134 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cedbcb..4341b6de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ 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 guessed type information for NXP tags, and reorganization of HF information part. (@FlUxIuS) - 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) 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..d4d2acd7 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -2,28 +2,258 @@ **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:** check all status responses in all commands with `hw raw` + +Beware, slots in protocol count from 0 to 7. + +### 1000: GET_APP_VERSION +* Command: no data +* Response: 2 bytes: `version_major|version_minor` +### 1001: CHANGE_DEVICE_MODE +* Command: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* Response: no data +### 1002: GET_DEVICE_MODE +* Command: no data +* Response: data: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +### 1003: SET_ACTIVE_SLOT +* Command: 1 byte. `slot_number` between 0 and 7 +* Response: no data +### 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. +* Response: no data + +**TODO:** remap `tag_specific_type_t` enum. Maybe dissociate LF & HF types in 2 enums +### 1005: SET_SLOT_DATA_DEFAULT +* 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. +* Response: no data + +**TODO:** remap `tag_specific_type_t` enum. Maybe dissociate LF & HF types in 2 enums +### 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 +### 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 + +**TODO:** rewrite cmd unpack +### 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`. + +**TODO:** rewrite cmd unpack +### 1009: SLOT_DATA_CONFIG_SAVE +* Command: no data +* Response: no data +### 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. +### 1011: GET_DEVICE_CHIP_ID +* Command: no data +* Response: 8 bytes. nRF `DEVICEID[8]` in Network byte order. +### 1012: GET_DEVICE_ADDRESS +* Command: no data +* Response: 6 bytes. nRF `DEVICEADDR[6]` in Network byte order. First 2 MSBits forced to `0b11` to match BLE static address. +### 1013: SAVE_SETTINGS +* Command: no data +* Response: no data +### 1014: RESET_SETTINGS +* Command: no data +* Response: no data +### 1015: SET_ANIMATION_MODE +* Command: 1 byte, according to `settings_animation_mode_t` enum. +* Response: no data +### 1016: GET_ANIMATION_MODE +* Command: no data +* Response: 1 byte, according to `settings_animation_mode_t` enum. +### 1017: GET_GIT_VERSION +* Command: no data +* Response: n bytes, a UTF-8 encoded string, no null terminator. +### 1018: GET_ACTIVE_SLOT +* Command: no data +* Response: 1 byte +### 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 + +**TODO:** remap `tag_specific_type_t` enum. Maybe dissociate LF & HF types in 2 enums +### 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. +### 1023: GET_ENABLED_SLOTS +* Command: no data +* Response: 8 bytes, 8 bools = `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 +### 1025: GET_BATTERY_INFO +* Command: no data +* Response: 3 bytes, `voltage[2]|percentage` +### 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. +### 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 +### 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. +### 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 +### 1030: SET_BLE_PAIRING_KEY +* Command: 6 bytes. 6 ASCII-encoded digits. +* Response: no data +### 1031: GET_BLE_PAIRING_KEY +* Command: no data +* Response: 6 bytes. 6 ASCII-encoded digits. +### 1032: DELETE_ALL_BLE_BONDS +* Command: no data +* Response: no data +### 1033: GET_DEVICE_MODEL +* Command: no data +* Response: 1 byte. `hw_version` aka `NRF_DFU_HW_VERSION` (0=Ultra, 1=Lite) +### 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) + +### 1035: GET_DEVICE_CAPABILITIES +* Command: no data +* Response: 2*n bytes, a list of supported commands IDs. +### 1036: GET_BLE_PAIRING_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +### 1037: SET_BLE_PAIRING_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +### 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]` + +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` +### 2002: MF1_DETECT_PRNG +* Command: no data +* Response: 1 byte, according to `mf1_nested_type_t` enum +### 2003: MF1_DETECT_DARKSIDE +* Command: no data +* Response: 1 byte, according to `mf1_darkside_status_t` enum +### 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]` +### 2005: MF1_DETECT_NT_DIST +* Command: 8 bytes: `type_known|block_known|key_known[6]` +* Response: 8 bytes: `uid[4]|dist[4]` +### 2006: MF1_NESTED_ACQUIRE +* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target` +* Response: N*9 bytes: N tuples of `nt[4]|nt_enc[4]|par` +### 2007: MF1_AUTH_ONE_KEY_BLOCK +* Command: 8 bytes: `type|block|key[6]` +* Response: no data +* Status will be `HF_TAG_OK` if auth succeeded, else `MF_ERR_AUTH` +### 2008: MF1_READ_ONE_BLOCK +* Command: 8 bytes: `type|block|key[6]` +* Response: 16 bytes: `block_data[16]` +### 2009: MF1_WRITE_ONE_BLOCK +* Command: 24 bytes: `type|block|key[6]|block_data[16]` +* Response: no data +### 3000: EM410X_SCAN +### 3001: EM410X_WRITE_TO_T55XX +### 4000: MF1_WRITE_EMU_BLOCK_DATA +### 4001: MF1_SET_ANTI_COLLISION_RES +* FIXME: rename as MF1_SET_ANTI_COLL_DATA ? +### 4002: MF1_SET_ANTI_COLLISION_INFO +* FIXME: not implemented, delete? +### 4003: MF1_SET_ATS_RESOURCE +* FIXME: not implemented +### 4004: MF1_SET_DETECTION_ENABLE +### 4005: MF1_GET_DETECTION_COUNT +### 4006: MF1_GET_DETECTION_LOG +### 4007: MF1_GET_DETECTION_STATUS +### 4008: MF1_READ_EMU_BLOCK_DATA +### 4009: MF1_GET_EMULATOR_CONFIG +### 4010: MF1_GET_GEN1A_MODE +### 4011: MF1_SET_GEN1A_MODE +### 4012: MF1_GET_GEN2_MODE +### 4013: MF1_SET_GEN2_MODE +### 4014: MF1_GET_BLOCK_ANTI_COLL_MODE +### 4015: MF1_SET_BLOCK_ANTI_COLL_MODE +### 4016: MF1_GET_WRITE_MODE +### 4017: MF1_SET_WRITE_MODE +### 4018: MF1_GET_ANTI_COLL_DATA +### 5000: EM410X_SET_EMU_ID +### 5001: EM410X_GET_EMU_ID + + +## 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. +- 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. 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 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. diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 9400edbd..c005696b 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,29 +24,41 @@ 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) { +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) { if (data[0] == 1) { #if defined(PROJECT_CHAMELEON_ULTRA) @@ -66,17 +78,15 @@ data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, } } -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; +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[] = {0}; + if (get_device_mode() == DEVICE_MODE_READER) { + resp_data[0] = 1; } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); + 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 +96,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,11 +145,11 @@ 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) { +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) { status = STATUS_DEVICE_SUCCESS; settings_set_animation_config(data[0]); @@ -140,21 +159,22 @@ data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, return data_frame_make(cmd, status, 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)); } -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) { +static 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]); @@ -166,7 +186,7 @@ data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t st return data_frame_make(cmd, status, length, (uint8_t *)(&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) { +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])) { settings_set_button_press_config(data[0], data[1]); status = STATUS_DEVICE_SUCCESS; @@ -177,7 +197,7 @@ data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t st return data_frame_make(cmd, status, 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) { +static 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]); @@ -189,7 +209,7 @@ data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16 return data_frame_make(cmd, status, length, (uint8_t *)(&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) { +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])) { settings_set_long_button_press_config(data[0], data[1]); status = STATUS_DEVICE_SUCCESS; @@ -200,12 +220,12 @@ data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16 return data_frame_make(cmd, status, 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)); } -data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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] == true || data[0] == false)) { settings_set_ble_pairing_enable(data[0]); status = STATUS_DEVICE_SUCCESS; @@ -217,12 +237,33 @@ data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t sta #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; + // 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, status, offset, payload); + } else { + return data_frame_make(cmd, status, 0, NULL); + } +} + +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) { + length = 1; + data = &support; } else { length = 0; data = NULL; @@ -230,28 +271,48 @@ data_frame_tx_t *cmd_processor_14a_scan(uint16_t cmd, uint16_t status, uint16_t return data_frame_make(cmd, status, length, data); } -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); -} - -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_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) { + length = 1; + data = &type; + } else { + length = 0; + data = NULL; + } + return data_frame_make(cmd, status, length, data); } -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_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) { + length = 1; + data = &darkside_status; + } else { + length = 0; + data = NULL; + } + return data_frame_make(cmd, status, length, data); } -data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - DarksideCore dc; +static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint8_t darkside_status; + // DarksideCore_t is PACKED and comprises only bytes so we can use it directly + DarksideCore_t dc; + } PACKED payload; if (length == 4) { - status = darkside_recover_key(data[1], data[0], data[2], data[3], &dc); + 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) { - length = sizeof(DarksideCore); - data = (uint8_t *)(&dc); + if (payload.darkside_status == DARKSIDE_OK) { + length = sizeof(payload); + data = (uint8_t *)&payload; + } else { + length = 1; + data = &payload.darkside_status; + } } else { length = 0; } @@ -262,28 +323,53 @@ data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t statu 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); +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; + + struct { + uint8_t uid[4]; + uint32_t distance; + } PACKED payload_resp; + + payload_t *payload = (payload_t *)data; + if (length == sizeof(payload_t)) { + 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) { - length = sizeof(NestedDist); - data = (uint8_t *)(&nd); + payload_resp.distance = U32HTONL(distance); + length = sizeof(payload_resp); + data = (uint8_t *)&payload_resp; } else { length = 0; + data = NULL; } } else { status = STATUS_PAR_ERR; length = 0; + data = 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); +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; + + payload_t *payload = (payload_t *)data; + if (length == sizeof(payload_t)) { + 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) { + // NestedCore_t is PACKED and comprises only bytes so we can use it directly length = sizeof(ncs); data = (uint8_t *)(&ncs); } else { @@ -296,9 +382,16 @@ data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, return data_frame_make(cmd, status, length, data); } -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]); +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; + + payload_t *payload = (payload_t *)data; + if (length == sizeof(payload_t)) { + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); pcd_14a_reader_mf1_unauth(); } else { status = STATUS_PAR_ERR; @@ -306,12 +399,19 @@ data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t sta 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; + + 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 (length == sizeof(payload_t)) { + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_read(data[1], block); + status = pcd_14a_reader_mf1_read(payload->block, block); if (status == HF_TAG_OK) { length = 16; } else { @@ -327,11 +427,19 @@ data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, return data_frame_make(cmd, status, length, 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]); +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; + + payload_t *payload = (payload_t *)data; + if (length == sizeof(payload_t)) { + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_write(data[1], &data[8]); + status = pcd_14a_reader_mf1_write(payload->block, payload->block_data); } else { length = 0; } @@ -341,13 +449,13 @@ data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status 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); } -data_frame_tx_t *cmd_processor_write_em410x_2_t57(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_em410x_write_to_t55XX(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 { @@ -359,14 +467,7 @@ data_frame_tx_t *cmd_processor_write_em410x_2_t57(uint16_t cmd, uint16_t status, #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); -} - -data_frame_tx_t *cmd_processor_set_slot_activated(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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) { change_slot_auto(data[0]); status = STATUS_DEVICE_SUCCESS; @@ -376,45 +477,68 @@ data_frame_tx_t *cmd_processor_set_slot_activated(uint16_t cmd, uint16_t status, return data_frame_make(cmd, status, 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_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; + + payload_t *payload = (payload_t *)data; + status = STATUS_PAR_ERR; + if (length == sizeof(payload_t)) { + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot < TAG_MAX_SLOT_NUM && tag_type != TAG_TYPE_UNKNOWN) { + tag_emulation_change_type(payload->num_slot, tag_type); + status = STATUS_DEVICE_SUCCESS; + } } return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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; 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]; - - tag_emulation_delete_data(slot_num, sense_type); + if (length == sizeof(payload_t) && payload->num_slot < TAG_MAX_SLOT_NUM && (payload->sense_type == TAG_SENSE_HF || payload->sense_type == TAG_SENSE_LF)) { + tag_emulation_delete_data(payload->num_slot, payload->sense_type); status = STATUS_DEVICE_SUCCESS; } 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_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; + + payload_t *payload = (payload_t *)data; + status = STATUS_PAR_ERR; + if (length == sizeof(payload_t)) { + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot < TAG_MAX_SLOT_NUM && tag_type != TAG_TYPE_UNKNOWN) { + 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_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]; +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; + status = STATUS_PAR_ERR; + if (length == sizeof(payload_t) && + payload->slot_index < TAG_MAX_SLOT_NUM && + payload->enabled <= 1) { + 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); @@ -432,36 +556,40 @@ data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, ui return data_frame_make(cmd, status, 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) { +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) { tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); @@ -473,7 +601,7 @@ data_frame_tx_t *cmd_processor_set_em410x_emu_id(uint16_t cmd, uint16_t status, return data_frame_make(cmd, status, 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,7 +613,7 @@ 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_mf1_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) { @@ -500,7 +628,7 @@ data_frame_tx_t *cmd_processor_get_mf1_anti_coll_data(uint16_t cmd, uint16_t sta 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) { +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] == 0 || data[0] == 1)) { nfc_tag_mf1_detection_log_clear(); nfc_tag_mf1_set_detection_enable(data[0]); @@ -511,7 +639,7 @@ data_frame_tx_t *cmd_processor_set_mf1_detection_enable(uint16_t cmd, uint16_t s return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_detection_status(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_status(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (nfc_tag_mf1_is_detection_enable()) { status = 1; } else { @@ -520,20 +648,19 @@ data_frame_tx_t *cmd_processor_get_mf1_detection_status(uint16_t cmd, uint16_t s return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); } -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); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(uint32_t), (uint8_t *)&count); } -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); + nfc_tag_mf1_auth_log_t *logs = mf1_get_auth_log(&count); if (length == 4) { if (count == 0xFFFFFFFF) { length = 0; @@ -543,7 +670,7 @@ data_frame_tx_t *cmd_processor_get_mf1_detection_log(uint16_t cmd, uint16_t stat // 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 = MIN(count - index, NETDATA_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)); length = length * sizeof(nfc_tag_mf1_auth_log_t); status = STATUS_DEVICE_SUCCESS; } else { @@ -558,7 +685,7 @@ data_frame_tx_t *cmd_processor_get_mf1_detection_log(uint16_t cmd, uint16_t stat return data_frame_make(cmd, status, length, resp); } -data_frame_tx_t *cmd_processor_set_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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)) { uint8_t block_index = data[0]; uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; @@ -579,7 +706,7 @@ data_frame_tx_t *cmd_processor_set_mf1_emulator_block(uint16_t cmd, uint16_t sta return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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) { uint8_t block_index = data[0]; uint8_t block_count = data[1]; @@ -595,14 +722,14 @@ data_frame_tx_t *cmd_processor_get_mf1_emulator_block(uint16_t cmd, uint16_t sta memcpy(p_block, info->memory[j], NFC_TAG_MF1_DATA_SIZE); } - return data_frame_make(cmd, status, result_length, result_buffer); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, result_length, result_buffer); } } return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } -data_frame_tx_t *cmd_processor_set_mf1_anti_collision_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_set_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; @@ -626,57 +753,63 @@ data_frame_tx_t *cmd_processor_set_mf1_anti_collision_res(uint16_t cmd, uint16_t return data_frame_make(cmd, status, 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) { +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 > 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; + if (slot < TAG_MAX_SLOT_NUM && (sense_type == TAG_SENSE_HF || sense_type == TAG_SENSE_LF)) { + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - 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; + 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; + } } else { - status = STATUS_FLASH_WRITE_FAIL; + status = STATUS_PAR_ERR; } } return data_frame_make(cmd, status, 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); + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } else { uint8_t buffer[36]; + uint8_t *resp_data = NULL; + uint16_t resp_length = 0; 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]; + if (slot < TAG_MAX_SLOT_NUM && (sense_type == TAG_SENSE_HF || sense_type == TAG_SENSE_LF)) { + 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; + resp_length = buffer[0]; + resp_data = &buffer[1]; + } else { + status = STATUS_FLASH_READ_FAIL; + } } else { - status = STATUS_FLASH_READ_FAIL; - length = 0; - data = NULL; + status = STATUS_PAR_ERR; } // must be called within stack allocation of buffer - return data_frame_make(cmd, status, length, data); + return data_frame_make(cmd, status, resp_length, resp_data); } } -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(); @@ -695,7 +828,7 @@ data_frame_tx_t *cmd_processor_get_mf1_info(uint16_t cmd, uint16_t status, uint1 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) { +static data_frame_tx_t *cmd_processor_mf1_get_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (nfc_tag_mf1_is_gen1a_magic_mode()) { status = 1; } else { @@ -704,7 +837,7 @@ data_frame_tx_t *cmd_processor_get_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t s return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); } -data_frame_tx_t *cmd_processor_set_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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] == 0 || data[0] == 1)) { nfc_tag_mf1_set_gen1a_magic_mode(data[0]); status = STATUS_DEVICE_SUCCESS; @@ -714,7 +847,7 @@ data_frame_tx_t *cmd_processor_set_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t s return data_frame_make(cmd, status, 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) { +static data_frame_tx_t *cmd_processor_mf1_get_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (nfc_tag_mf1_is_gen2_magic_mode()) { status = 1; } else { @@ -723,7 +856,7 @@ data_frame_tx_t *cmd_processor_get_mf1_gen2_magic_mode(uint16_t cmd, uint16_t st return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); } -data_frame_tx_t *cmd_processor_set_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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] == 0 || data[0] == 1)) { nfc_tag_mf1_set_gen2_magic_mode(data[0]); status = STATUS_DEVICE_SUCCESS; @@ -733,7 +866,7 @@ data_frame_tx_t *cmd_processor_set_mf1_gen2_magic_mode(uint16_t cmd, uint16_t st return data_frame_make(cmd, status, 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) { +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) { if (nfc_tag_mf1_is_use_mf1_coll_res()) { status = 1; } else { @@ -742,7 +875,7 @@ data_frame_tx_t *cmd_processor_get_mf1_use_coll_res(uint16_t cmd, uint16_t statu return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); } -data_frame_tx_t *cmd_processor_set_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +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] == 0 || data[0] == 1)) { nfc_tag_mf1_set_use_mf1_coll_res(data[0]); status = STATUS_DEVICE_SUCCESS; @@ -752,7 +885,7 @@ data_frame_tx_t *cmd_processor_set_mf1_use_coll_res(uint16_t cmd, uint16_t statu return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_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; @@ -766,7 +899,7 @@ data_frame_tx_t *cmd_processor_get_mf1_write_mode(uint16_t cmd, uint16_t status, 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) { +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] >= 0 || data[0] <= 3)) { uint8_t mode = data[0]; if (mode == 0) { @@ -785,30 +918,30 @@ data_frame_tx_t *cmd_processor_set_mf1_write_mode(uint16_t cmd, uint16_t status, return data_frame_make(cmd, status, 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) { +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_CONNECT_KEY_LEN_MAX, // 6 + BLE_PAIRING_KEY_LEN, // 6 settings_get_ble_connect_key() // Get key point from config ); } -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) { +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) { // 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) { + for (uint8_t i = 0; i < BLE_PAIRING_KEY_LEN; i++) { + if (data[i] < '0' || data[i] > '9') { is_valid_key = false; break; } @@ -826,7 +959,7 @@ data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status return data_frame_make(cmd, status, 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,7 +972,7 @@ 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; @@ -853,7 +986,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 +999,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 +1032,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_MF1_SET_ANTI_COLLISION_RES, NULL, cmd_processor_mf1_set_anti_collision_res, 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_STATUS, NULL, cmd_processor_mf1_get_detection_status, 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_MF1_GET_ANTI_COLL_DATA, NULL, cmd_processor_mf1_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 +1111,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 +1129,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..8d3a7b6a 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)); diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index f8595012..68fdcabe 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,27 @@ // 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_MF1_SET_ANTI_COLLISION_RES (4001) +//FIXME: not implemented +#define DATA_CMD_MF1_SET_ANTI_COLLISION_INFO (4002) +//FIXME: not implemented +#define DATA_CMD_MF1_SET_ATS_RESOURCE (4003) +#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_STATUS (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_MF1_GET_ANTI_COLL_DATA (4018) // // ****************************************************************** @@ -114,7 +117,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..19f560b5 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -388,7 +388,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..ef64bb1e 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h @@ -136,7 +136,7 @@ typedef struct { } 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..9c8fca31 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..d1328990 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,28 @@ #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; +// this struct is also used in the fw/cli protocol, therefore PACKED 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 +47,7 @@ typedef struct { uint8_t ks_list[8]; uint8_t nr[4]; uint8_t ar[4]; -} DarksideCore; +} PACKED DarksideCore_t; #ifdef __cplusplus @@ -50,13 +60,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 +76,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..ece234cb 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 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..10936707 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -17,12 +17,25 @@ from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError from chameleon_utils import CLITree +# 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 +44,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 +153,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: @@ -220,6 +234,8 @@ def on_exec(self, args: argparse.Namespace): return self.device_com.open(args.port) print(" { Chameleon connected } ") + self.device_com.commands = self.cmd.get_device_capabilities() + except Exception as e: print(f"Chameleon Connect fail: {str(e)}") @@ -235,10 +251,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 +264,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,8 +291,8 @@ 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})') @@ -286,21 +302,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 +346,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 +382,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 +392,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 +413,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. @@ -456,9 +473,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 +509,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 +555,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,8 +573,8 @@ 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: + resp = self.cmd.mf1_write_one_block(param.block, param.type, param.key, param.data) + if resp: print(f" - {colorama.Fore.GREEN}Write done.{colorama.Style.RESET_ALL}") else: print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}") @@ -570,7 +590,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,7 +601,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 + data_bytes = self.cmd.mf1_get_detection_count().data count = int.from_bytes(data_bytes, "little", signed=False) print(f" - MF1 detection log count = {count}") @@ -629,13 +649,13 @@ def decrypt_by_list(self, rs: list): 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 = int.from_bytes(self.cmd.mf1_get_detection_count().data, "little", signed=False) if count == 0: print(" - No detection log to download") return print(f" - MF1 detection log count = {count}, start download", end="") while index < count: - tmp = self.cmd.get_mf1_detection_log(index).data + tmp = self.cmd.mf1_get_detection_log(index).data recv_count = int(len(tmp) / HFMFDetectionDecrypt.detection_log_size) index += recv_count buffer.extend(tmp) @@ -709,7 +729,7 @@ def on_exec(self, args: argparse.Namespace): block_data = buffer[index: index + 16] index += 16 # load to device - self.cmd.set_mf1_block_data(block, block_data) + self.cmd.mf1_write_emu_block_data(block, block_data) print('.', end='') block += 1 print("\n - Load success") @@ -735,9 +755,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: @@ -752,7 +772,7 @@ def on_exec(self, args: argparse.Namespace): with open(file, 'wb') as fd: block = 0 while block < block_count: - response = self.cmd.get_mf1_block_data(block, 1) + response = self.cmd.mf1_read_emu_block_data(block, 1) print('.', end='') block += 1 if content_type == 'hex': @@ -789,16 +809,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') @@ -836,7 +856,7 @@ def on_exec(self, args: argparse.Namespace): else: raise Exception("UID must be hex") - self.cmd.set_mf1_anti_collision_res(sak, atqa, uid) + self.cmd.mf1_set_anti_collision_res(sak, atqa, uid) print(" - Set anti-collision resources success") @@ -846,7 +866,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def scan(self): - resp: chameleon_com.Response = self.cmd.get_mf1_anti_coll_data() + # FIXME: + resp: chameleon_com.Response = self.cmd.mf1_get_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']}") @@ -868,7 +889,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.read_em_410x() + resp = self.cmd.em410x_scan() id_hex = resp.data.hex() print(f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") @@ -908,7 +929,7 @@ def before_exec(self, args: argparse.Namespace): 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) + self.cmd.em410x_write_to_t55xx(id_bytes) print(f" - EM410x ID(10H): {id_hex} write done.") @@ -962,7 +983,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def get_slot_name(self, slot, sense): try: - return self.cmd.get_slot_tag_nick_name(slot, sense).data.decode() + return self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") except UnexpectedResponseError: return "Empty" except UnicodeDecodeError: @@ -970,20 +991,20 @@ def get_slot_name(self, slot, sense): # 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() 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 ""}:') 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'{chameleon_cmd.TagSpecificType(slotinfo[chameleon_cmd.SlotNumber.to_fw(slot)][0])}') 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])}') + f'{chameleon_cmd.TagSpecificType(slotinfo[chameleon_cmd.SlotNumber.to_fw(slot)][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 + config = self.cmd.mf1_get_emulator_config().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"}') @@ -1001,7 +1022,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.") @@ -1100,7 +1121,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: 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(id_bytes) print(' - Set em410x tag id success.') @@ -1111,7 +1132,7 @@ 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()}') @@ -1133,7 +1154,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 +1170,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 +1181,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 +1210,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 +1222,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 +1239,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 +1261,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 +1272,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 +1285,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 +1305,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,9 +1322,7 @@ 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" percentage -> {percentage}%") @@ -1325,10 +1341,10 @@ 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]) + 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" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}short{colorama.Style.RESET_ALL}:" f" {button_fn}") print(f" usage: {button_fn.usage()}") @@ -1359,9 +1375,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,9 +1390,9 @@ 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"{colorama.Fore.GREEN}{resp.decode(encoding='ascii')}{colorama.Style.RESET_ALL}") if args.key != None: if len(args.key) != 6: @@ -1449,5 +1465,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..6df20add 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -3,14 +3,14 @@ import chameleon_com import chameleon_status -from chameleon_utils import expect_response +from chameleon_utils import expect_response, expect_response_ng 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,61 @@ 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 +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_MF1_SET_ANTI_COLLISION_RES = 4001 +# FIXME: not implemented +DATA_CMD_MF1_SET_ANTI_COLLISION_INFO = 4002 +# FIXME: not implemented +DATA_CMD_MF1_SET_ATS_RESOURCE = 4003 +DATA_CMD_MF1_SET_DETECTION_ENABLE = 4004 +DATA_CMD_MF1_GET_DETECTION_COUNT = 4005 +DATA_CMD_MF1_GET_DETECTION_LOG = 4006 + +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_MF1_GET_ANTI_COLL_DATA = 4018 + +DATA_CMD_EM410X_SET_EMU_ID = 5000 +DATA_CMD_EM410X_GET_EMU_ID = 5001 @enum.unique @@ -212,6 +220,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 +353,149 @@ 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_ng(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) + resp.data = struct.unpack('!BB', resp.data) + return resp - def get_device_chip_id(self) -> str: + @expect_response_ng(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) + resp.data = resp.data.hex() + return resp - def get_device_address(self) -> str: + @expect_response_ng(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) + resp.data = resp.data.hex() + return resp + @expect_response_ng(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) + resp.data = resp.data.decode('utf-8') + return resp + + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_mode(self): + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE) + 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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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): + @expect_response_ng(chameleon_status.Device.HF_TAG_OK) + def hf14a_scan(self): """ 14a tags in the scanning field :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_14A_TAG, 0x00) - - def detect_mf1_support(self): + 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 + + @expect_response_ng(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_ng(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_ng(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): + @expect_response_ng(chameleon_status.Device.HF_TAG_OK) + 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): + @expect_response_ng(chameleon_status.Device.HF_TAG_OK) + 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): + @expect_response_ng(chameleon_status.Device.HF_TAG_OK) + 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 +504,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): + @expect_response_ng([chameleon_status.Device.HF_TAG_OK, chameleon_status.Device.MF_ERR_AUTH]) + 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 +523,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): + @expect_response_ng(chameleon_status.Device.HF_TAG_OK) + def mf1_read_one_block(self, block, type_value, key): """ read one mf1 block :param block: @@ -436,14 +537,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): + @expect_response_ng(chameleon_status.Device.HF_TAG_OK) + def mf1_write_one_block(self, block, type_value, key, block_data): """ Write mf1 single block :param block: @@ -452,23 +550,21 @@ 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: bytearray): """ Write EM410X card number into T55XX :param id_bytes: ID card number @@ -478,40 +574,47 @@ def write_em_410x_to_t55xx(self, id_bytes: bytearray): old_keys = [[0x51, 0x24, 0x36, 0x48], [0x19, 0x92, 0x04, 0x27]] if len(id_bytes) != 5: raise ValueError("The id bytes length must equal 5") + # FIXME: 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) + return self.device.send_cmd_sync(DATA_CMD_EM410X_WRITE_TO_T55XX, data) + @expect_response_ng(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) + resp.data = [struct.unpack_from('!HH', resp.data, i) + for i in range(0, len(resp.data), struct.calcsize('!HH'))] + return resp + @expect_response_ng(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) + resp.data = resp.data[0] + return resp - @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_activated(self, slot_index: SlotNumber): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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) + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): """ Set the label type of the simulated card of the current card slot @@ -522,12 +625,10 @@ 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) + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseType): """ Delete a sense type for a specific slot. @@ -535,10 +636,10 @@ 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) + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType): """ Set the data of the simulated card in the specified card slot as the default data @@ -548,13 +649,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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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,13 +661,11 @@ 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: bytearray): """ Set the card number simulated by EM410x :param id_bytes: byte of the card number @@ -576,45 +673,46 @@ def set_em410x_sim_id(self, id_bytes: bytearray): """ if len(id_bytes) != 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_bytes) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SET_EMU_ID, data) - def get_em410x_sim_id(self): + 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): + 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) + return self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_COUNT) @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: """ + # FIXME: 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) + return self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_LOG, data) @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: bytearray): """ Set the block data of the analog card of MF1 :param block_start: Start setting the location of block data, including this location @@ -622,19 +720,18 @@ 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): + 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 mf1_set_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray): """ Set the anti-collision resource information of the MF1 analog card :param sak: sak bytes @@ -642,14 +739,15 @@ def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytea :param uid: card number array :return: """ + # FIXME: 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) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_ANTI_COLLISION_RES, data) - @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def set_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): """ Set the anti-collision resource information of the MF1 analog card :param slot: Card slot number @@ -658,13 +756,11 @@ def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, nam :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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType): """ Set the anti-collision resource information of the MF1 analog card :param slot: Card slot number @@ -672,11 +768,10 @@ def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType): :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): + def mf1_get_emulator_config(self): """ Get array of Mifare Classic emulators settings: [0] - mf1_is_detection_enable (mfkey32) @@ -686,120 +781,149 @@ 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) + return self.device.send_cmd_sync(DATA_CMD_MF1_GET_EMULATOR_CONFIG) - def set_mf1_gen1a_mode(self, enabled: bool): + 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): + 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): + 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): + 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_ng(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_ng(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) + resp.data = resp.data[0] + return resp + @expect_response_ng(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_ng(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_ng(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_ng(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_ng(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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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) + 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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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) + 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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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) + 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): + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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) + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_ble_connect_key(self, key: str): """ Set config of ble connect key @@ -810,44 +934,50 @@ 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_ng(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) +# FIXME: device reset ?? 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) + # expected response checked within the function def get_device_capabilities(self): """ Get (and set) 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") - + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES) + commands = [x[0] for x in struct.iter_unpack('!H',resp.data)] + except chameleon_com.CMDInvalidException: + print("Chameleon does not understand get_device_capabilities command. Please update firmware") return commands - def get_device_type(self): + @expect_response_ng(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) + resp.data = resp.data[0] + return resp + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_settings(self): """ Get all possible settings @@ -858,33 +988,48 @@ 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 - - def get_mf1_anti_coll_data(self): + 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.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 mf1_get_anti_coll_data(self): """ Get data from current slot (UID/SAK/ATQA) :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_ANTI_COLL_DATA, 0x00) + return self.device.send_cmd_sync(DATA_CMD_MF1_GET_ANTI_COLL_DATA) - def get_ble_pairing_enable(self) -> bool: + @expect_response_ng(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 - - @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) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE) + resp.data, = struct.unpack('!?', resp.data) + return resp + + @expect_response_ng(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + 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 +1037,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..ef6e322e 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -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 """ @@ -329,7 +324,7 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list 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 +343,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 index 14edc5fe..9d8323b9 100644 --- a/software/script/chameleon_cstruct.py +++ b/software/script/chameleon_cstruct.py @@ -1,7 +1,7 @@ """ From bytes to c struct parser(Chameleon data response) """ - +# FIXME: replace by struct.unpack? def bytes_to_u32(byte_array): """ @@ -11,67 +11,6 @@ def bytes_to_u32(byte_array): """ 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 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..b95c9d2a 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -69,6 +69,34 @@ def error_throwing_func(*args, **kwargs): return decorator +# TODO: should be used everywhere possible and replace/rename @expect_response +def expect_response_ng(accepted_responses: Union[int, list[int]]): + """ + Decorator for wrapping a Chameleon CMD function to check its response + for expected return codes and throwing an exception otherwise + """ + if isinstance(accepted_responses, int): + accepted_responses = [accepted_responses] + + def decorator(func): + @wraps(func) + def error_throwing_func(*args, **kwargs): + ret = func(*args, **kwargs) + + if ret.status not in accepted_responses: + if ret.status in chameleon_status.Device and ret.status in chameleon_status.message: + raise UnexpectedResponseError( + chameleon_status.message[ret.status]) + else: + raise UnexpectedResponseError( + f"Unexpected response and unknown status {ret.status}") + + return ret.data + + return error_throwing_func + + return decorator + class CLITree: """