This huge commit tries to enhance several things related to the fw/cli protocol. Generally, the idea is to be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors. docs/protocol.md got heavily updated Many commands have been renamed for consistency. you are invited to adapt your client for easier maintenance Guidelines, also written in docs/protocol.md "New data payloads: guidelines for developers": - Now protocol data exchanged over USB or BLE are defined in netdata.h as packed structs and values are stored in Network byte order (=Big Endian) - Command-specific payloads are defined in their respective cmd_processor handler in app_cmd.c and chameleon_cmd.py - Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. - If single byte of data to return, still use a 1-byte `data`, not `status`. - Use unambiguous types such as `uint16_t`, not `int` or `enum`. Cast explicitly `int` and `enum` to `uint_t` of proper size - Use Network byte order for 16b and 32b integers - Macros `U16NTOHS`, `U32NTOHL` must be used on reception of a command payload. - Macros `U16HTONS`, `U32HTONL` must be used on creation of a response payload. - In Python, use the modifier `!` with all `struct.pack`/`struct.unpack` - Concentrate payload parsing in the handlers, avoid further parsing in their callers. This is true for the firmware and the client. - In cmd_processor handlers: don't reuse input `length`/`data` parameters for creating the response content - Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python - Use the exact same command and fields names in firmware and in client, use function names matching the command names for their handlers unless there is a very good reason not to do so. This helps grepping around. Names must start with a letter, not a number, because some languages require it (e.g. `14a_scan` not possible in Python) - Respect commands order in `m_data_cmd_map`, `data_cmd.h` and `chameleon_cmd.py` definitions - Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to `data_cmd.h` and `chameleon_cmd.py` with some `FIXME: to be implemented` comment - Validate data before using it, both when receiving command data in the firmware and when receiving response data in the client. - Validate response status in client. Disruptive changes: - GET_DEVICE_CAPABILITIES: list of cmds in data are now really Big Endian Note: the initial attempt to use macros PP_HTONS were actually considering wrongly that the platform was Big Endian (BYTE_ORDER was actually undefined) while it is actually Little Endian. - GET_APP_VERSION: response is now a tuple of bytes: major|minor (previously it was in reversed order as a single uint16_t in Little Endian) - SET_SLOT_TAG_TYPE: tag_type now on 2 bytes, to prepare remapping of its enum - SET_SLOT_DATA_DEFAULT: tag_type now on 2 bytes, to prepare remapping of its enum - GET_SLOT_INFO: tag_type now on 2 bytes, to prepare remapping of its enum - GET_DEVICE_CHIP_ID: now returns its 64b ID following Network byte order (previously, bytes were in the reverse order) - GET_DEVICE_ADDRESS: now returns its 56b address following Network byte order (previously, bytes were in the reverse order). CLI does not reverse the response anymore so it displays the same value as before. - MF1_GET_DETECTION_COUNT: now returns its 32b value following Network byte order (previously Little Endian) - GET_GIT_VERSION response status is now STATUS_DEVICE_SUCCESS - GET_DEVICE_MODEL response status is now STATUS_DEVICE_SUCCESS - MF1_READ_EMU_BLOCK_DATA response status is now STATUS_DEVICE_SUCCESS - GET_DEVICE_CAPABILITIES response status is now STATUS_DEVICE_SUCCESS - HF14A_SCAN: entirely new response format, room for ATS and multiple tags - MF1_DETECT_SUPPORT response status is now HF_TAG_OK and support is indicated as bool in 1 byte of data - MF1_DETECT_PRNG response status is now HF_TAG_OK and prng_type is returned in 1 byte of data with a new enum mf1_prng_type_t == MifareClassicPrngType - MF1_DETECT_DARKSIDE response status is now HF_TAG_OK and darkside_status is returned in 1 byte of data with a new enum mf1_darkside_status_t == MifareClassicDarksideStatus - MF1_DARKSIDE_ACQUIRE response status is now HF_TAG_OK and darkside_status is returned in 1 byte of data. If OK, followed by 24 bytes as previously - MF1_GET_ANTI_COLL_DATA: in case slot does not contain anticoll data, instead of STATUS_PAR_ERR, now it returns STATUS_DEVICE_SUCCESS with empty data - MF1_SET_ANTI_COLL_DATA and MF1_GET_ANTI_COLL_DATA now use the same data format as HF14A_SCAN For clients to detect Ultra/Lite with older firmwares, one can issue the GET_APP_VERSION and urge the user to flash his device if needed. On older firmwares, it will return a status=b'\x00' and data=b'\x00\x01' while up-to-date firmwares will return status=STATUS_DEVICE_SUCCESS and data greater or equal to b'\x01\x00' (v1.0). Other changes: cf CHANGELOG, and probably a few small changes I forgot about.. TODO: - remap `tag_specific_type_t` enum to allow future tags (e.g. LF tags) without reshuffling enum and affecting users stored cards - TEST!
18 KiB
Protocol description
WIP
Frame format
The communication between the firmware and the client is made of frames structured as follows:
- SOF:
1 byte, "Start-Of-Frame byte" represents the start of a packet, and must be equal to0x11. - LRC1:
1 byte, LRC overSOFbyte, therefore must be equal to0xEF. - 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.
- From client to firmware, the status is always
- LEN:
2 bytes, length of theDATAfield, maximum is512. - LRC2:
1 byte, LRC overCMD|STATUS|LENbytes. - DATA:
LEN bytes, data to be sent or received, maximum is512 bytes. This payload depends on the exact command or response to command being used. See Packet payloads below. - LRC3:
1 byte, LRC overDATAbytes.
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 byte order.
- The total length of the packet is
LEN + 10bytes, therefore it is between10and522bytes. - The LRC (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)
Data payloads
Each command and response have their own payload formats.
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 for more info.
- TODO: remap
tag_specific_type_tenum. Maybe dissociate LF & HF types in 2 enums - TODO: num_to_bytes bytes_to_num
- TODO: mf1_darkside_acquire /nested acquire deep PACKED struct...
- FIXME: mf1_get_emulator_config with bits -> bytes (5) with 4 bools <> mf1_get_detection_log with bitfield (2)...
Beware, slots in protocol count from 0 to 7 (and from 1 to 8 in the CLI...).
In the following list, "CLI" refers to one typical CLI command using the described protocol command. But it's not a 1:1 match, there can be other protocol commands used by the CLI command and there can be other CLI commands using the same protocol command...
1000: GET_APP_VERSION
- Command: no data
- Response: 2 bytes:
version_major|version_minor - CLI: cf
hw version
1001: CHANGE_DEVICE_MODE
- Command: 1 byte.
0x00=emulator mode,0x01=reader mode - Response: no data
- CLI: cf
hw mode set
1002: GET_DEVICE_MODE
- Command: no data
- Response: data: 1 byte.
0x00=emulator mode,0x01=reader mode - CLI: cf
hw mode get
1003: SET_ACTIVE_SLOT
- Command: 1 byte.
slot_numberbetween 0 and 7 - Response: no data
- CLI: cf
hw slot change
1004: SET_SLOT_TAG_TYPE
- Command: 3 bytes.
slot_number|tag_type[2]withslot_numberbetween 0 and 7 andtag_typeaccording totag_specific_type_tenum, U16 in Network byte order. - Response: no data
- CLI: cf
hw slot type
1005: SET_SLOT_DATA_DEFAULT
- Command: 3 bytes.
slot_number|tag_type[2]withslot_numberbetween 0 and 7 andtag_typeU16 according totag_specific_type_tenum, U16 in Network byte order. - Response: no data
- CLI: cf
hw slot init
1006: SET_SLOT_ENABLE
- Command: 2 bytes.
slot_number|enablewithslot_numberbetween 0 and 7 andenable=0x01to enable,0x00to disable - Response: no data
- CLI: cf
hw slot enable
1007: SET_SLOT_TAG_NICK
- Command: 2+N bytes.
slot_number|sense_type|name[N]withslot_numberbetween 0 and 7,sense_typeaccording totag_sense_type_tenum andnamea UTF-8 encoded string of max 32 bytes, no null terminator. - Response: no data
- CLI: cf
hw slot nick set
1008: GET_SLOT_TAG_NICK
- Command: 2 bytes.
slot_number|sense_typewithslot_numberbetween 0 and 7 andsense_typeaccording totag_sense_type_tenum. - Response: a UTF-8 encoded string of max 32 bytes, no null terminator. If no nick name has been recorded in Flash, response status is
STATUS_FLASH_READ_FAIL. - CLI: cf
hw slot nick get
1009: SLOT_DATA_CONFIG_SAVE
- Command: no data
- Response: no data
- CLI: cf
hw slot update
1010: ENTER_BOOTLOADER
- Command: no data
- Response: this special command does not return and will interrupt the communication link while rebooting in bootloader mode, needed for DFU.
- CLI: cf
hw dfu
1011: GET_DEVICE_CHIP_ID
- Command: no data
- Response: 8 bytes. nRF
DEVICEID[8]U64 in Network byte order. - CLI: cf
hw chipid get
1012: GET_DEVICE_ADDRESS
- Command: no data
- Response: 6 bytes. nRF
DEVICEADDR[6]U48 in Network byte order. First 2 MSBits forced to0b11to match BLE static address. - CLI: cf
hw address get
1013: SAVE_SETTINGS
- Command: no data
- Response: no data
- CLI: cf
hw settings store
1014: RESET_SETTINGS
- Command: no data
- Response: no data
- CLI: cf
hw settings reset
1015: SET_ANIMATION_MODE
- Command: 1 byte, according to
settings_animation_mode_tenum. - Response: no data
- CLI: cf
hw settings animation set
1016: GET_ANIMATION_MODE
- Command: no data
- Response: 1 byte, according to
settings_animation_mode_tenum. - CLI: cf
hw settings animation get
1017: GET_GIT_VERSION
- Command: no data
- Response: n bytes, a UTF-8 encoded string, no null terminator.
- CLI: cf
hw version
1018: GET_ACTIVE_SLOT
- Command: no data
- Response: 1 byte
- CLI: cf
hw slot list
1019: GET_SLOT_INFO
- Command: no data
- Response: 32 bytes, 8 tuples
hf_tag_type[2]|lf_tag_type[2]according totag_specific_type_tenum, for slots from 0 to 7, U16 in Network byte order. - CLI: cf
hw slot list
1020: WIPE_FDS
- Command: no data
- Response: no data. Status is
STATUS_DEVICE_SUCCESSorSTATUS_FLASH_WRITE_FAIL. The device will reboot shortly after this command. - CLI: cf
hw factory_reset
1023: GET_ENABLED_SLOTS
- Command: no data
- Response: 8 bytes, 8 bool =
0x00or0x01, for slots from 0 to 7
1024: DELETE_SLOT_SENSE_TYPE
- Command: 2 bytes.
slot_number|sense_typewithslot_numberbetween 0 and 7 andsense_typeaccording totag_sense_type_tenum. - Response: no data
- CLI: cf
hw factory_reset
1025: GET_BATTERY_INFO
- Command: no data
- Response: 3 bytes,
voltage[2]|percentage. Voltage: U16 in Network byte order. - CLI: cf
hw battery
1026: GET_BUTTON_PRESS_CONFIG
- Command: 1 byte. Char
AorB(a/btolerated too) - Response: 1 byte,
button_functionaccording tosettings_button_function_tenum. - CLI: cf
hw settings btnpress get
1027: SET_BUTTON_PRESS_CONFIG
- Command: 2 bytes.
button|button_functionwithbuttoncharAorB(a/btolerated too) andbutton_functionaccording tosettings_button_function_tenum. - Response: no data
- CLI: cf
hw settings btnpress set
1028: GET_LONG_BUTTON_PRESS_CONFIG
- Command: 1 byte. Char
AorB(a/btolerated too) - Response: 1 byte,
button_functionaccording tosettings_button_function_tenum. - CLI: cf
hw settings btnpress get
1029: SET_LONG_BUTTON_PRESS_CONFIG
- Command: 2 bytes.
button|button_functionwithbuttoncharAorB(a/btolerated too) andbutton_functionaccording tosettings_button_function_tenum. - Response: no data
- CLI: cf
hw settings btnpress set
1030: SET_BLE_PAIRING_KEY
- Command: 6 bytes. 6 ASCII-encoded digits.
- Response: no data
- CLI: cf
hw settings blekey
1031: GET_BLE_PAIRING_KEY
- Command: no data
- Response: 6 bytes. 6 ASCII-encoded digits.
- CLI: cf
hw settings blekey
1032: DELETE_ALL_BLE_BONDS
- Command: no data
- Response: no data
- CLI: cf
hw ble bonds clear
1033: GET_DEVICE_MODEL
- Command: no data
- Response: 1 byte.
hw_versionakaNRF_DFU_HW_VERSION(0=Ultra, 1=Lite) - CLI: cf
hw version
1034: GET_DEVICE_SETTINGS
- Command: no data
- Response: 14 bytes
settings_current_version=5animation_mode, cf GET_ANIMATION_MODEbtn_press_A, cf GET_BUTTON_PRESS_CONFIGbtn_press_B, cf GET_BUTTON_PRESS_CONFIGbtn_long_press_A, cf GET_LONG_BUTTON_PRESS_CONFIGbtn_long_press_B, cf GET_LONG_BUTTON_PRESS_CONFIGble_pairing_enable, cf GET_BLE_PAIRING_ENABLEble_pairing_key[6], cf GET_BLE_PAIRING_KEY
- CLI: unused
1035: GET_DEVICE_CAPABILITIES
- Command: no data
- Response: 2*n bytes, a list of supported commands IDs.
- CLI: used internally on connect
1036: GET_BLE_PAIRING_ENABLE
- Command: no data
- Response: 1 byte, bool =
0x00or0x01 - CLI: cf
hw settings blepair
1037: SET_BLE_PAIRING_ENABLE
- Command: 1 byte, bool =
0x00or0x01 - Response: no data
- CLI: cf
hw settings blepair
2000: HF14A_SCAN
- Command: no data
- Response: N bytes:
tag1_data|tag2_data|...with each tag:uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]. UID, ATQA, SAK and ATS as bytes. - CLI: cf
hf 14a scan
Notes:
- remind that if no tag is present, status will be
HF_TAG_NOand Response empty. - at the moment, the firmware supports only one tag, but get your client ready for more!
atslenmust not be confused withats[0]==TL. Soatslen|ats=00means no ATS while0100would be an empty ATS.
2001: MF1_DETECT_SUPPORT
- Command: no data
- Response: 1 byte, bool =
0x00or0x01 - CLI: cf
hf 14a info
2002: MF1_DETECT_PRNG
- Command: no data
- Response: 1 byte, according to
mf1_nested_type_tenum - CLI: cf
hf 14a info
2003: MF1_DETECT_DARKSIDE
- Command: no data
- Response: 1 byte, according to
mf1_darkside_status_tenum - CLI: unused
- FIXME: always
CANT_FIX_NTor watchdog reset on static nonce cards
2004: MF1_DARKSIDE_ACQUIRE
- Command: 4 bytes:
type_target|block_target|first_recover|sync_max - Response: 1 byte if Darkside failed, according to
mf1_darkside_status_tenum, else 25 bytesdarkside_status|uid[4]|nt1[4]|par[4]|ks1[4]|nr[4]|ar[4]darkside_statusuid[4]U32 (format expected bydarksidetool)nt1[4]U32par[4]U32ks1[4]U32nr[4]U32ar[4]U32
- CLI: cf
hf mf darkside - FIXME: always
CANT_FIX_NTor watchdog reset on static nonce cards
2005: MF1_DETECT_NT_DIST
- Command: 8 bytes:
type_known|block_known|key_known[6]. Key as 6 bytes. - Response: 8 bytes:
uid[4]|dist[4]uid[4]U32 (format expected bynestedtool)dist[4]U32
- CLI: cf
hf mf nested
2006: MF1_NESTED_ACQUIRE
- Command: 10 bytes:
type_known|block_known|key_known[6]|type_target|block_target. Key as 6 bytes. - Response: N*9 bytes: N tuples of
nt[4]|nt_enc[4]|parnt[4]U32nt_enc[4]U32par
- CLI: cf
hf mf nested
2007: MF1_AUTH_ONE_KEY_BLOCK
- Command: 8 bytes:
type|block|key[6]. Key as 6 bytes. - Response: no data
- Status will be
HF_TAG_OKif auth succeeded, elseMF_ERR_AUTH - CLI: cf
hf mf nested
2008: MF1_READ_ONE_BLOCK
- Command: 8 bytes:
type|block|key[6]. Key as 6 bytes. - Response: 16 bytes:
block_data[16] - CLI: cf
hf mf rdbl
2009: MF1_WRITE_ONE_BLOCK
- Command: 24 bytes:
type|block|key[6]|block_data[16]. Key as 6 bytes. - Response: no data
- CLI: cf
hf mf wrbl
3000: EM410X_SCAN
- Command: no data
- Response: 5 bytes.
id[5]. ID as 5 bytes. - CLI: cf
lf em read
3001: EM410X_WRITE_TO_T55XX
- Command: 9+N*4 bytes:
id[5]|new_key[4]|old_key1[4]|old_key2[4]|...(N>=1). . ID as 5 bytes. Keys as 4 bytes. - Response: no data
- CLI: cf
lf em write
4000: MF1_WRITE_EMU_BLOCK_DATA
- Command: 1+N*16 bytes:
block_start|block_data1[16]|block_data2[16]|...(1<=N<=31) - Response: no data
- CLI: cf
hf mf eload
4001: HF14A_SET_ANTI_COLL_DATA
- Command: N bytes:
uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]. UID, ATQA, SAK and ATS as bytes. - Response: no data
- CLI: cf
hf mf sim
4004: MF1_SET_DETECTION_ENABLE
- Command: 1 byte, bool =
0x00or0x01 - Response: no data
- CLI: cf
hf detection enable
4005: MF1_GET_DETECTION_COUNT
- Command: no data
- Response: 4 bytes,
count[4], U32 in Network byte order. - CLI: cf
hf detection count
4006: MF1_GET_DETECTION_LOG
- Command: 4 bytes,
index, U32 in Network byte order. - Response: N*18 bytes. 0<=N<=28
block...|is_nested|is_key_b1-byte bitfield, starting from LSBuid[4]?nt[4]?nr[4]?ar[4]?
- CLI: cf
hf detection decrypt
4007: MF1_GET_DETECTION_ENABLE
- Command: no data
- Response: 1 byte, bool =
0x00or0x01 - CLI: cf
hw slot list
4008: MF1_READ_EMU_BLOCK_DATA
- Command: 2 bytes:
block_start|block_countwith 1<=block_count<=32 - Response:
block_count*16 bytes - CLI: cf
hf mf eread
4009: MF1_GET_EMULATOR_CONFIG
- Command: no data
- Response: 5 bytes
detection, cf MF1_GET_DETECTION_ENABLEgen1a_mode, cf MF1_GET_GEN1A_MODEgen2_mode, cf MF1_GET_GEN2_MODEblock_anti_coll_mode, cf MF1_GET_BLOCK_ANTI_COLL_MODEwrite_mode, cf MF1_GET_WRITE_MODE
- CLI: cf
hw slot list
4010: MF1_GET_GEN1A_MODE
- Command: no data
- Response: 1 byte, bool =
0x00or0x01 - CLI: unused
4011: MF1_SET_GEN1A_MODE
- Command: 1 byte, bool =
0x00or0x01 - Response: no data
- CLI: cf
hf mf settings
4012: MF1_GET_GEN2_MODE
- Command: no data
- Response: 1 byte, bool =
0x00or0x01 - CLI: unused
4013: MF1_SET_GEN2_MODE
- Command: 1 byte, bool =
0x00or0x01 - Response: no data
- CLI: cf
hf mf settings
4014: MF1_GET_BLOCK_ANTI_COLL_MODE
- Command: no data
- Response: 1 byte, bool =
0x00or0x01 - CLI: unused
4015: MF1_SET_BLOCK_ANTI_COLL_MODE
- Command: 1 byte, bool =
0x00or0x01 - Response: no data
- CLI: cf
hf mf settings
4016: MF1_GET_WRITE_MODE
- Command: no data
- Response: 1 byte, according to
nfc_tag_mf1_write_mode_takaMifareClassicWriteModeenum - CLI: unused
4017: MF1_SET_WRITE_MODE
- Command: 1 byte, according to
nfc_tag_mf1_write_mode_takaMifareClassicWriteModeenum - Response: no data
- CLI: cf
hf mf settings
4018: HF14A_GET_ANTI_COLL_DATA
- Command: no data
- Response: no data or N bytes:
uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]. UID, ATQA, SAK and ATS as bytes. - CLI: cf
hf mf info
5000: EM410X_SET_EMU_ID
- Command: 5 bytes.
id[5]. ID as 5 bytes. - Response: no data
- CLI: cf
lf em sim set
5001: EM410X_GET_EMU_ID
- Command: no data
- Response: 5 bytes.
id[5]. ID as 5 bytes. - CLI: cf
lf em sim get
New data payloads: guidelines for developers
If you need to define new payloads for new commands, try to follow these guidelines.
Guideline: Verbose and explicit
Be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors
Guideline: Structs
- Define C
structfor cmd/resp data greater than a single byte, use and abuse ofstruct.pack/struct.unpackin Python. So one can understand the payload format at a simple glimpse. Exceptions toCstruct are when the formats are of variable length (but Pythonstructis still flexible enough to cope with such formats!) - Avoid hardcoding offsets, use
sizeof(),offsetof(struct, field)in C andstruct.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.
Guideline: unambiguous types
- Use unambiguous types such as
uint16_t, notintorenum. Cast explicitlyintandenumtouint_tof proper size - Use Network byte order for 16b and 32b integers
- Macros
U16NTOHS,U32NTOHLmust be used on reception of a command payload. - Macros
U16HTONS,U32HTONLmust be used on creation of a response payload. - In Python, use the modifier
!with allstruct.pack/struct.unpack
- Macros
Guideline: payload parsing in handlers
- Concentrate payload parsing in the handlers, avoid further parsing in their callers. Callers should not care about the protocol. This is true for the firmware and the client.
- In cmd_processor handlers: don't reuse input
length/dataparameters 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_scannot possible in Python) - Respect commands order in
m_data_cmd_map,data_cmd.handchameleon_cmd.pydefinitions - Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to
data_cmd.handchameleon_cmd.pywith someFIXME: to be implementedcomment
Guideline: Validate status and data
- Validate response status in client before parsing data.
- Validate data before using it.
