Global firmware+CLI versions are following the semantic versioning logic mostly regarding the protocol version, so third party clients (GUIs, mobile apps, SDKs) can rely on firmware version to know their level of compatibility.
Given a version number MAJOR.MINOR.PATCH, we will increment the:
- MAJOR version when we are breaking the existing protocol format
- MINOR version when we are extending the protocol format in a backward compatible manner (new commands,...)
- PATCH version when we are releasing bugfixes not affecting the protocol description
Besides compatibility with a given firmware version, third party clients may choose to offer to the users the possibility to follow a stable release channel (installing only tagged releases) or the development channel (installing latest commits).
For the stable channel, a client compatible with versions X.y.z can accept any version > X.y'.z' but should refuse to work with a version X'>X.
For the development channel, a client compatible with versions X.y.z can accept any latest commit unless a tag X'.0.0 with X'>X is present in the repo, indicating that the corresponding commit and all the commits above are incompatible with the client version. There is still a non negligible risk that breaking changes are pushed while forgetting about putting a new tag, or artefacts being built before the tag being pushed. Here be dragons... It's always a good practice for the client to validate whatever data is transmitted by the firmware, and fail gracefully in case of hiccups.
Cf GET_APP_VERSION and GET_GIT_VERSION.
When GET_GIT_VERSION
returns only a tag and no commit hash info (on a release tag), one can query the corresponding hash with the GitHub API, e.g.
"4747d3884d21e0df8549e3029a920ea390e0b00a"
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 overSOF
byte, 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 theDATA
field, maximum is512
. - LRC2:
1 byte
, LRC overCMD|STATUS|LEN
bytes. - 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 overDATA
bytes.
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 + 10
bytes, therefore it is between10
and522
bytes. - 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)
Each command and response have their own payload formats.
Standard response status is STATUS_SUCCESS
for general commands, STATUS_HF_TAG_OK
for HF commands and STATUS_LF_TAG_OK
for LF commands.
See Guidelines for more info.
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...
- Command: no data
- Response: 2 bytes:
version_major|version_minor
- CLI: cf
hw version
- Command: 1 byte.
0x00
=emulator mode,0x01
=reader mode - Response: no data
- CLI: cf
hw mode
- Command: no data
- Response: data: 1 byte.
0x00
=emulator mode,0x01
=reader mode - CLI: cf
hw mode
- Command: 1 byte.
slot_number
between 0 and 7 - Response: no data
- CLI: cf
hw slot change
- Command: 3 bytes.
slot_number|tag_type[2]
withslot_number
between 0 and 7 andtag_type
according totag_specific_type_t
enum, U16 in Network byte order. - Response: no data
- CLI: cf
hw slot type
- Command: 3 bytes.
slot_number|tag_type[2]
withslot_number
between 0 and 7 andtag_type
U16 according totag_specific_type_t
enum, U16 in Network byte order. - Response: no data
- CLI: cf
hw slot init
- Command: 3 bytes.
slot_number|sense_type|enable
withslot_number
between 0 and 7,sense_type
according totag_sense_type_t
enum andenable
=0x01
to enable,0x00
to disable - Response: no data
- CLI: cf
hw slot enable
/hw slot disable
- Command: 2+N bytes.
slot_number|sense_type|name[N]
withslot_number
between 0 and 7,sense_type
according totag_sense_type_t
enum andname
a UTF-8 encoded string of max 32 bytes, no null terminator. - Response: no data
- CLI: cf
hw slot nick
- Command: 2 bytes.
slot_number|sense_type
withslot_number
between 0 and 7 andsense_type
according totag_sense_type_t
enum. - Response: a UTF-8 encoded string of max 32 bytes, no null terminator. If no nick name has been recorded in Flash, response status is
STATUS_FLASH_READ_FAIL
. - CLI: cf
hw slot nick
- Command: no data
- Response: no data
- CLI: cf
hw slot store
- 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
- Command: no data
- Response: 8 bytes. nRF
DEVICEID[8]
U64 in Network byte order. - CLI: cf
hw chipid
- Command: no data
- Response: 6 bytes. nRF
DEVICEADDR[6]
U48 in Network byte order. First 2 MSBits forced to0b11
to match BLE static address. - CLI: cf
hw address
- Command: no data
- Response: no data
- CLI: cf
hw settings store
- Command: no data
- Response: no data
- CLI: cf
hw settings reset
- Command: 1 byte, according to
settings_animation_mode_t
enum. - Response: no data
- CLI: cf
hw settings animation
- Command: no data
- Response: 1 byte, according to
settings_animation_mode_t
enum. - CLI: cf
hw settings animation
- Command: no data
- Response: n bytes, a UTF-8 encoded string, no null terminator.
- CLI: cf
hw version
Notes: the returned string is the output of git describe --abbrev=7 --dirty --always --tags --match "v*.*"
so, depending on the status of the repo it can be
- a short tag, e.g.
v2.0.0
if the firmware is built from the tagged commit - a longer tag indicating how far it is from the latest tag and 7 nibbles of its commit hash, prepended with
g
, e.g. 5 commits away from v2.0.0:v2.0.0-5-g617d6d0
- a long tag finishing with
-dirty
if the local repo contains changes not yet committed, e.g.v2.0.0-5-g617d6d0-dirty
- Command: no data
- Response: 1 byte
- CLI: cf
hw slot list
- Command: no data
- Response: 32 bytes, 8 tuples
hf_tag_type[2]|lf_tag_type[2]
according totag_specific_type_t
enum, for slots from 0 to 7, U16 in Network byte order. - CLI: cf
hw slot list
- Command: no data
- Response: no data. Status is
STATUS_SUCCESS
orSTATUS_FLASH_WRITE_FAIL
. The device will reboot shortly after this command. - CLI: cf
hw factory_reset
- Command: 2 bytes.
slot_number|sense_type
withslot_number
between 0 and 7 andsense_type
according totag_sense_type_t
enum. - Response: no data
- CLI: cf
hw slot nick
- Command: no data
- Response: 16 bytes, 8*2 bool =
0x00
or0x01
, 2 bytes for each slot from 0 to 7, asenabled_hf|enabled_lf
- CLI: cf
hw slot list
- Command: 2 bytes.
slot_number|sense_type
withslot_number
between 0 and 7 andsense_type
according totag_sense_type_t
enum. - Response: no data
- CLI: cf
hw slot delete
- Command: no data
- Response: 3 bytes,
voltage[2]|percentage
. Voltage: U16 in Network byte order. - CLI: cf
hw battery
Notes: wait about 5 seconds after wake-up, before querying the battery status, else the device won't be able to give a proper measure and will return zeroes.
- Command: 1 byte. Char
A
orB
(a
/b
tolerated too) - Response: 1 byte,
button_function
according tosettings_button_function_t
enum. - CLI: cf
hw settings btnpress
- Command: 2 bytes.
button|button_function
withbutton
charA
orB
(a
/b
tolerated too) andbutton_function
according tosettings_button_function_t
enum. - Response: no data
- CLI: cf
hw settings btnpress
- Command: 1 byte. Char
A
orB
(a
/b
tolerated too) - Response: 1 byte,
button_function
according tosettings_button_function_t
enum. - CLI: cf
hw settings btnpress
- Command: 2 bytes.
button|button_function
withbutton
charA
orB
(a
/b
tolerated too) andbutton_function
according tosettings_button_function_t
enum. - Response: no data
- CLI: cf
hw settings btnpress
- Command: 6 bytes. 6 ASCII-encoded digits.
- Response: no data
- CLI: cf
hw settings blekey
- Command: no data
- Response: 6 bytes. 6 ASCII-encoded digits.
- CLI: cf
hw settings blekey
- Command: no data
- Response: no data
- CLI: cf
hw settings bleclearbonds
- Command: no data
- Response: 1 byte.
hw_version
akaNRF_DFU_HW_VERSION
according tochameleon_device_type_t
enum (0=Ultra, 1=Lite) - CLI: cf
hw version
- Command: no data
- Response: 14 bytes
settings_current_version
=5
animation_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
- Command: no data
- Response: 2*n bytes, a list of supported commands IDs.
- CLI: used internally on connect
- Command: no data
- Response: 1 byte, bool =
0x00
or0x01
- CLI: cf
hw settings blepair
- Command: 1 byte, bool =
0x00
or0x01
- Response: no data
- CLI: cf
hw settings blepair
- 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
STATUS_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 withats[0]
==TL
. Soatslen|ats
=00
means no ATS while0100
would be an empty ATS.
- Command: no data
- Response: 1 byte, bool =
0x00
or0x01
- CLI: cf
hf 14a info
- Command: no data
- Response: 1 byte, according to
mf1_nested_type_t
enum - CLI: cf
hf 14a info
- Command: 10 bytes:
type_known|block_known|key_known[6]|type_target|block_target
. Key as 6 bytes. - Response: 4+N*8 bytes:
uid[4]
followed by N tuples ofnt[4]|nt_enc[4]
. All values as U32. - CLI: cf
hf mf nested
on static nonce tag
- Command: 4 bytes:
type_target|block_target|first_recover|sync_max
. Type=0x60 for key A, 0x61 for key B. - Response: 1 byte if Darkside failed, according to
mf1_darkside_status_t
enum, else 33 bytesdarkside_status|uid[4]|nt1[4]|par[8]|ks1[8]|nr[4]|ar[4]
darkside_status
uid[4]
U32 (format expected bydarkside
tool)nt1[4]
U32par[8]
U64ks1[8]
U64nr[4]
U32ar[4]
U32
- CLI: cf
hf mf darkside
- Command: 8 bytes:
type_known|block_known|key_known[6]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B. - Response: 8 bytes:
uid[4]|dist[4]
uid[4]
U32 (format expected bynested
tool)dist[4]
U32
- CLI: cf
hf mf nested
- Command: 10 bytes:
type_known|block_known|key_known[6]|type_target|block_target
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B. - Response: N*9 bytes: N tuples of
nt[4]|nt_enc[4]|par
nt[4]
U32nt_enc[4]
U32par
- CLI: cf
hf mf nested
- Command: 8 bytes:
type|block|key[6]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B. - Response: no data
- Status will be
STATUS_HF_TAG_OK
if auth succeeded, elseSTATUS_MF_ERR_AUTH
- CLI: cf
hf mf nested
- Command: 8 bytes:
type|block|key[6]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B. - Response: 16 bytes:
block_data[16]
- CLI: cf
hf mf rdbl
- Command: 24 bytes:
type|block|key[6]|block_data[16]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B. - Response: no data
- CLI: cf
hf mf wrbl
- Command: : 5+N bytes:
options|resp_timeout_ms[2]|bitlen[2]
followed by data to be transmitted, withoptions
a 1-byte BigEndian bitfield, so starting from MSB:activate_rf_field
:1wait_response
:1append_crc
:1auto_select
:1keep_rf_field
:1check_response_crc
:1reserved
:2
- Response: data sent by the card
- CLI: cf
hf 14a raw
- Command: 21 bytes:
src_type|src_block|src_key[6]|operator|operand[4]|dst_type|dst_block|dst_key[6]
. Key as 6 bytes. Type=0x60
for key A,0x61
for key B. Operator=0xC0
for decrement,0xC1
for increment,0xC2
for restore. Operand as I32 in Network byte order. - Response: no data
- CLI: cf
hf mf value
- Command: 10+N*6 bytes:
mask[10]|keys[N][6]
(1<=N<=83)mask
: 40 sectors, 2 bits/sector, MSB:0A|0B|1A|1B|...|39A|39B
.0b1
represent to skip checking the key.
- Response: 490 bytes:
found[10]|sectorKey[40][2][6]
.found
: 40 sectors, 2 bits/sector, MSB:0A|0B|1A|1B|...|39A|39B
.0b1
represent the key is found.sectorKey
: 40 sectors, 2 keys/sector, 6 bytes/key:key0A[6]|key0B[6]|key1A[6]|key1B[6]|...|key39A[6]|key39B[6]
- CLI: cf
hf mf fchk
- Command: no data
- Response: 5 bytes.
id[5]
. ID as 5 bytes. - CLI: cf
lf em 410x read
- 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 410x write
- Command: 1+N*16 bytes:
block_start|block_data1[16]|block_data2[16]|...
(1<=N<=31) - Response: no data
- CLI: cf
hf mf eload
- 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 econfig
/hf mfu econfig
- Command: 1 byte, bool =
0x00
or0x01
- Response: no data
- CLI: cf
hf mf econfig
- Command: no data
- Response: 4 bytes,
count[4]
, U32 in Network byte order. - CLI: cf
hf mf elog
- Command: 4 bytes,
index
, U32 in Network byte order. - Response: N*18 bytes. 0<=N<=28
block
...|is_nested|is_key_b
1-byte bitfield, starting from LSBuid[4]
?nt[4]
?nr[4]
?ar[4]
?
- CLI: cf
hf mf elog
- Command: no data
- Response: 1 byte, bool =
0x00
or0x01
- CLI: cf
hw slot list
- Command: 2 bytes:
block_start|block_count
with 1<=block_count
<=32 - Response:
block_count
*16 bytes - CLI: cf
hf mf eread
- 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
hf mf econfig
- Command: no data
- Response: 1 byte, bool =
0x00
or0x01
- CLI: unused
- Command: 1 byte, bool =
0x00
or0x01
- Response: no data
- CLI: cf
hf mf econfig
- Command: no data
- Response: 1 byte, bool =
0x00
or0x01
- CLI: unused
- Command: 1 byte, bool =
0x00
or0x01
- Response: no data
- CLI: cf
hf mf econfig
- Command: no data
- Response: 1 byte, bool =
0x00
or0x01
- CLI: unused
- Command: 1 byte, bool =
0x00
or0x01
- Response: no data
- CLI: cf
hf mf econfig
- Command: no data
- Response: 1 byte, according to
nfc_tag_mf1_write_mode_t
akaMifareClassicWriteMode
enum - CLI: unused
- Command: 1 byte, according to
nfc_tag_mf1_write_mode_t
akaMifareClassicWriteMode
enum - Response: no data
- CLI: cf
hf mf econfig
- 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
hw slot list
/hf mf econfig
/hf mfu econfig
- Command: no data
- Response: 1 byte where a non-zero value indicates that UID magic mode is enabled for the current slot.
- CLI: cf
hf mfu econfig
- Command: 1 byte where a non-zero value indicates that UID magic mode should be enabled for the current slot, otherwise disabled.
- Response: no data
- CLI: cf
hf mfu econfig --enable-uid-magic
/hf mfu econfig --disable-uid-magic
- Command: 2 bytes: one for first page index, one for count of pages to be read.
- Response:
4 * n
bytes wheren
is the number if pages to be read - CLI: cf
hf mfu eview
- Command: 2 +
n * 4
bytes: one for first page index, one for count of pages to be read,n * 4
forn
pages data. - Response: no data
- CLI: unused
- Command: no data
- Response: 8 version data bytes.
- CLI: cf
hf mfu econfig
- Command: 8 version data bytes.
- Response: no data
- CLI: cf
hf mfu econfig --set-version <hex>
- Command: no data
- Response: 32 signature data bytes.
- CLI: cf
hf mfu econfig
- Command: 32 signature data bytes.
- Response: no data
- CLI: cf
hf mfu econfig --set-signature <hex>
- Command: 1 byte for the counter index
- Response: 3 bytes for the counter value (big-endian) + 1 byte for tearing where
0xBD
means tearing flag is not set. - CLI: cf
hf mfu ercnt
- Command: 1 byte where the lower 7 bits are the counter index and the top bit indicates whether tearing event flag should be reset + 3 bytes of the counter value (big-endian).
- Response: no data
- CLI: cf
hf mfu ewcnt
- Command: no data
- Response: 1 byte for the old value of the unsuccessful auth counter.
- CLI: cf
hf mfu econfig --reset-auth-cnt
- Command: no data
- Response: 1 byte is the number of pages available in the current card slot
- CLI: unused
- Command: 5 bytes.
id[5]
. ID as 5 bytes. - Response: no data
- CLI: cf
lf em 410x econfig
- Command: no data
- Response: 5 bytes.
id[5]
. ID as 5 bytes. - CLI: cf
lf em 410x econfig
If you need to define new payloads for new commands, try to follow these guidelines.
Be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors
- Define C
struct
for cmd/resp data greater than a single byte, use and abuse ofstruct.pack
/struct.unpack
in Python. So one can understand the payload format at a simple glimpse. Exceptions toC
struct are when the formats are of variable length (but Pythonstruct
is still flexible enough to cope with such formats!) - Avoid hardcoding offsets, use
sizeof()
,offsetof(struct, field)
in C andstruct.calcsize()
in Python - For complex bitfield structs, exceptionally you can use ctypes in Python. Beware ctypes.BigEndianStructure bitfield will be parsed in the firmware in the reverse order, from LSB to MSB.
If single byte of data to return, still use a 1-byte data
, not status
. Standard response status is STATUS_SUCCESS
for general commands, STATUS_HF_TAG_OK
for HF commands and STATUS_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.
- Use unambiguous types such as
uint16_t
, notint
orenum
. Cast explicitlyint
andenum
touint_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 allstruct.pack
/struct.unpack
- Macros
- Concentrate payload parsing in the handlers, avoid further parsing in their callers. Callers should not care about the protocol. This is true for the firmware and the client.
- In cmd_processor handlers: don't reuse input
length
/data
parameters for creating the response content
- 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
andchameleon_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
andchameleon_cmd.py
with someFIXME: to be implemented
comment
- Validate response status in client before parsing data.
- Validate data before using it.
- some
num_to_bytes
bytes_to_num
could usehton*
,ntoh*
instead, to make endianess explicit - some commands are using bitfields (e.g. mf1_get_detection_log (sending directly the flash stored format) and hf14a_raw) while some commands are spreading bits into 0x00/0x01 bytes (e.g. mf1_get_emulator_config)
- describe flash storage formats