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:
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:
1 byte
, “Start-Of-Frame byte” represents the start of a packet, and must be equal to 0x11
.1 byte
, LRC over SOF
byte, therefore must be equal to 0xEF
.2 bytes
, each command have been assigned a unique number (e.g. DATA_CMD_SET_SLOT_TAG_NICK
= 1007
).2 bytes
.
0x0000
.2 bytes
, length of the DATA
field, maximum is 512
.1 byte
, LRC over CMD|STATUS|LEN
bytes.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 below.1 byte
, LRC over DATA
bytes.Notes:
LEN + 10
bytes, therefore it is between 10
and 522
bytes.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…
version_major|version_minor
hw version
0x00
=emulator mode, 0x01
=reader modehw mode
0x00
=emulator mode, 0x01
=reader modehw mode
slot_number
between 0 and 7hw slot change
slot_number|tag_type[2]
with slot_number
between 0 and 7 and tag_type
according to tag_specific_type_t
enum, U16 in Network byte order.hw slot type
slot_number|tag_type[2]
with slot_number
between 0 and 7 and tag_type
U16 according to tag_specific_type_t
enum, U16 in Network byte order.hw slot init
slot_number|sense_type|enable
with slot_number
between 0 and 7, sense_type
according to tag_sense_type_t
enum and enable
= 0x01
to enable, 0x00
to disablehw slot enable
/hw slot disable
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.hw slot nick
slot_number|sense_type
with slot_number
between 0 and 7 and sense_type
according to tag_sense_type_t
enum.STATUS_FLASH_READ_FAIL
.hw slot nick
hw slot store
hw dfu
DEVICEID[8]
U64 in Network byte order.hw chipid
DEVICEADDR[6]
U48 in Network byte order. First 2 MSBits forced to 0b11
to match BLE static address.hw address
hw settings store
hw settings reset
settings_animation_mode_t
enum.hw settings animation
settings_animation_mode_t
enum.hw settings animation
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
v2.0.0
if the firmware is built from the tagged commitg
, e.g. 5 commits away from v2.0.0: v2.0.0-5-g617d6d0
-dirty
if the local repo contains changes not yet committed, e.g. v2.0.0-5-g617d6d0-dirty
hw slot list
hf_tag_type[2]|lf_tag_type[2]
according to tag_specific_type_t
enum, for slots from 0 to 7, U16 in Network byte order.hw slot list
STATUS_SUCCESS
or STATUS_FLASH_WRITE_FAIL
. The device will reboot shortly after this command.hw factory_reset
slot_number|sense_type
with slot_number
between 0 and 7 and sense_type
according to tag_sense_type_t
enum.hw slot nick
0x00
or 0x01
, 2 bytes for each slot from 0 to 7, as enabled_hf|enabled_lf
hw slot list
slot_number|sense_type
with slot_number
between 0 and 7 and sense_type
according to tag_sense_type_t
enum.hw slot delete
voltage[2]|percentage
. Voltage: U16 in Network byte order.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.
A
or B
(a
/b
tolerated too)button_function
according to settings_button_function_t
enum.hw settings btnpress
button|button_function
with button
char A
or B
(a
/b
tolerated too) and button_function
according to settings_button_function_t
enum.hw settings btnpress
A
or B
(a
/b
tolerated too)button_function
according to settings_button_function_t
enum.hw settings btnpress
button|button_function
with button
char A
or B
(a
/b
tolerated too) and button_function
according to settings_button_function_t
enum.hw settings btnpress
hw settings blekey
hw settings blekey
hw settings bleclearbonds
hw_version
aka NRF_DFU_HW_VERSION
according to chameleon_device_type_t
enum (0=Ultra, 1=Lite)hw version
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_KEY0x00
or 0x01
hw settings blepair
0x00
or 0x01
hw settings blepair
tag1_data|tag2_data|...
with each tag: uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]
. UID, ATQA, SAK and ATS as bytes.hf 14a scan
Notes:
STATUS_HF_TAG_NO
and Response empty.atslen
must not be confused with ats[0]
==TL
. So atslen|ats
= 00
means no ATS while 0100
would be an empty ATS.
0x00
or 0x01
hf 14a info
mf1_nested_type_t
enumhf 14a info
type_known|block_known|key_known[6]|type_target|block_target
. Key as 6 bytes.uid[4]
followed by N tuples of nt[4]|nt_enc[4]
. All values as U32.hf mf nested
on static nonce tag
type_target|block_target|first_recover|sync_max
. Type=0x60 for key A, 0x61 for key B.mf1_darkside_status_t
enum,
else 33 bytes darkside_status|uid[4]|nt1[4]|par[8]|ks1[8]|nr[4]|ar[4]
darkside_status
uid[4]
U32 (format expected by darkside
tool)nt1[4]
U32par[8]
U64ks1[8]
U64nr[4]
U32ar[4]
U32hf mf darkside
type_known|block_known|key_known[6]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.uid[4]|dist[4]
uid[4]
U32 (format expected by nested
tool)dist[4]
U32hf mf nested
type_known|block_known|key_known[6]|type_target|block_target
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.nt[4]|nt_enc[4]|par
nt[4]
U32nt_enc[4]
U32par
hf mf nested
type|block|key[6]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.STATUS_HF_TAG_OK
if auth succeeded, else STATUS_MF_ERR_AUTH
hf mf nested
type|block|key[6]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.block_data[16]
hf mf rdbl
type|block|key[6]|block_data[16]
. Key as 6 bytes. Type=0x60 for key A, 0x61 for key B.hf mf wrbl
options|resp_timeout_ms[2]|bitlen[2]
followed by data to be transmitted, with options
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
:2hf 14a raw
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.hf mf value
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.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]
hf mf fchk
id[5]
. ID as 5 bytes.lf em 410x read
id[5]|new_key[4]|old_key1[4]|old_key2[4]|...
(N>=1). . ID as 5 bytes. Keys as 4 bytes.lf em 410x write
block_start|block_data1[16]|block_data2[16]|...
(1<=N<=31)hf mf eload
uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]
. UID, ATQA, SAK and ATS as bytes.hf mf econfig
/hf mfu econfig
0x00
or 0x01
hf mf econfig
count[4]
, U32 in Network byte order.hf mf elog
index
, U32 in Network byte order.block
...|is_nested|is_key_b
1-byte bitfield, starting from LSBuid[4]
?nt[4]
?nr[4]
?ar[4]
?hf mf elog
0x00
or 0x01
hw slot list
block_start|block_count
with 1<=block_count
<=32block_count
*16 byteshf mf eread
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_MODEhf mf econfig
0x00
or 0x01
0x00
or 0x01
hf mf econfig
0x00
or 0x01
0x00
or 0x01
hf mf econfig
0x00
or 0x01
0x00
or 0x01
hf mf econfig
nfc_tag_mf1_write_mode_t
aka MifareClassicWriteMode
enumnfc_tag_mf1_write_mode_t
aka MifareClassicWriteMode
enumhf mf econfig
uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]
. UID, ATQA, SAK and ATS as bytes.hw slot list
/hf mf econfig
/hf mfu econfig
hf mfu econfig
hf mfu econfig --enable-uid-magic
/hf mfu econfig --disable-uid-magic
4 * n
bytes where n
is the number if pages to be readhf mfu eview
n * 4
bytes: one for first page index, one for count of pages to be read, n * 4
for n
pages data.hf mfu econfig
hf mfu econfig --set-version <hex>
hf mfu econfig
hf mfu econfig --set-signature <hex>
0xBD
means tearing flag is not set.hf mfu ercnt
hf mfu ewcnt
hf mfu econfig --reset-auth-cnt
id[5]
. ID as 5 bytes.lf em 410x econfig
id[5]
. ID as 5 bytes.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
struct
for cmd/resp data greater than a single byte, use and abuse of struct.pack
/struct.unpack
in Python. So one can understand the payload format at a simple glimpse. Exceptions to C
struct are when the formats are of variable length (but Python struct
is still flexible enough to cope with such formats!)sizeof()
, offsetof(struct, field)
in C and struct.calcsize()
in PythonIf 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.
uint16_t
, not int
or enum
. Cast explicitly int
and enum
to uint_t
of proper sizeU16NTOHS
, U32NTOHL
must be used on reception of a command payload.U16HTONS
, U32HTONL
must be used on creation of a response payload.!
with all struct.pack
/struct.unpack
length
/data
parameters for creating the response content
14a_scan
not possible in Python)m_data_cmd_map
, data_cmd.h
and chameleon_cmd.py
definitionsdata_cmd.h
and chameleon_cmd.py
with some FIXME: to be implemented
comment
num_to_bytes
bytes_to_num
could use hton*
, ntoh*
instead, to make endianess explicit