Merge pull request #984 from DoniyorI/feature/nfc-v-write-support

nfc: add ISO15693-3 and SLIX write-back support
This commit is contained in:
MMX
2026-04-01 16:32:19 +03:00
committed by GitHub
11 changed files with 358 additions and 7 deletions
@@ -109,9 +109,75 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
}
static NfcCommand
nfc_scene_write_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso15693_3);
NfcApp* instance = context;
Iso15693_3Poller* poller = event.instance;
const Iso15693_3PollerEvent* iso15693_3_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) {
const Iso15693_3Data* write_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolIso15693_3);
const Iso15693_3Data* card_data = iso15693_3_poller_get_data(poller);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
if(card_data->system_info.block_count != write_data->system_info.block_count ||
card_data->system_info.block_size != write_data->system_info.block_size) {
furi_string_set(instance->text_box_store, "Incompatible card\n(block count/size\nmismatch)");
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventPollerFailure);
} else {
Iso15693_3Error error = Iso15693_3ErrorNone;
const uint8_t block_size = write_data->system_info.block_size;
const uint8_t* write_block_data =
(const uint8_t*)simple_array_cget_data(write_data->block_data);
const uint8_t* card_block_data =
(const uint8_t*)simple_array_cget_data(card_data->block_data);
for(uint16_t i = 0; i < write_data->system_info.block_count; i++) {
if(iso15693_3_is_block_locked(write_data, i)) continue;
// Skip blocks that already contain the correct data
if(memcmp(
write_block_data + i * block_size,
card_block_data + i * block_size,
block_size) == 0)
continue;
error = iso15693_3_poller_write_block(
poller, write_block_data + i * block_size, i, block_size);
if(error == Iso15693_3ErrorInternal) {
// Block is locked on the target card, skip it
error = Iso15693_3ErrorNone;
continue;
}
if(error != Iso15693_3ErrorNone) break;
}
if(error == Iso15693_3ErrorNone) {
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventPollerSuccess);
} else {
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventPollerFailure);
}
}
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_write_on_enter_iso15693_3(NfcApp* instance) {
furi_string_set(instance->text_box_store, "Apply the\ntarget card now");
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolIso15693_3);
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_iso15693_3, instance);
}
const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid |
NfcProtocolFeatureMoreInfo,
NfcProtocolFeatureMoreInfo | NfcProtocolFeatureWrite,
.scene_info =
{
@@ -155,7 +221,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_enter = nfc_scene_write_on_enter_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
@@ -9,6 +9,10 @@
#include "../nfc_protocol_support_common.h"
#include "../nfc_protocol_support_gui_common.h"
#include <string.h>
#define TAG "SlixWrite"
static void nfc_scene_info_on_enter_slix(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
@@ -106,8 +110,105 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
}
static NfcCommand nfc_scene_write_poller_callback_slix(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolSlix);
NfcApp* instance = context;
SlixPoller* poller = event.instance;
const SlixPollerEvent* slix_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(slix_event->type == SlixPollerEventTypeReady) {
const SlixData* write_data = nfc_device_get_data(instance->nfc_device, NfcProtocolSlix);
const Iso15693_3Data* write_iso_data = slix_get_base_data(write_data);
const Iso15693_3Data* card_iso_data =
slix_get_base_data((const SlixData*)nfc_poller_get_data(instance->poller));
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
if(card_iso_data->system_info.block_count != write_iso_data->system_info.block_count ||
card_iso_data->system_info.block_size != write_iso_data->system_info.block_size) {
furi_string_set(
instance->text_box_store,
"Incompatible card\n(block count/size\nmismatch)");
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventPollerFailure);
} else {
SlixError error = SlixErrorNone;
uint16_t failed_block = 0;
const uint8_t block_size = write_iso_data->system_info.block_size;
const uint8_t* write_data_ptr =
(const uint8_t*)simple_array_cget_data(write_iso_data->block_data);
const uint8_t* card_data_ptr =
(const uint8_t*)simple_array_cget_data(card_iso_data->block_data);
for(uint16_t i = 0; i < write_iso_data->system_info.block_count; i++) {
if(iso15693_3_is_block_locked(write_iso_data, i)) continue;
// Skip blocks that already contain the correct data
if(memcmp(
write_data_ptr + i * block_size,
card_data_ptr + i * block_size,
block_size) == 0)
continue;
error = slix_poller_write_block(
poller, write_data_ptr + i * block_size, i, block_size);
if(error == SlixErrorInternal) {
// Block is locked on the target card, skip it
error = SlixErrorNone;
continue;
}
if(error != SlixErrorNone) {
failed_block = i;
FURI_LOG_E(TAG, "Write failed: block %u, error %d", failed_block, (int)error);
break;
}
}
if(error == SlixErrorNone) {
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventPollerSuccess);
} else {
const char* err_name;
switch(error) {
case SlixErrorTimeout:
err_name = "Timeout";
break;
case SlixErrorFormat:
err_name = "BadCRC";
break;
case SlixErrorNotSupported:
err_name = "NotSupported";
break;
case SlixErrorInternal:
err_name = "Locked";
break;
case SlixErrorWrongPassword:
err_name = "WrongPwd";
break;
default:
err_name = "Unknown";
break;
}
furi_string_printf(
instance->text_box_store, "Block %u: %s", failed_block, err_name);
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventPollerFailure);
}
}
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_write_on_enter_slix(NfcApp* instance) {
furi_string_set(instance->text_box_store, "Apply the\ntarget card now");
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix);
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_slix, instance);
}
const NfcProtocolSupportBase nfc_protocol_support_slix = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo |
NfcProtocolFeatureWrite,
.scene_info =
{
@@ -151,7 +252,7 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = {
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_enter = nfc_scene_write_on_enter_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
@@ -15,6 +15,11 @@ extern "C" {
#define ISO15693_3_FDT_LISTEN_FC (4320U)
#define ISO15693_3_POLL_POLL_MIN_US (1500U)
/* NVM write time varies by tag type (ICODE SLIX: 9.6ms, generic: up to 20ms).
* ISO 15693-3 write commands require a longer FWT than reads to account for
* EEPROM programming delays before the tag responds. 20ms covers all known tags. */
#define ISO15693_3_FDT_WRITE_POLL_FC (271200U)
#define ISO15693_3_REQ_FLAG_SUBCARRIER_1 (0U << 0)
#define ISO15693_3_REQ_FLAG_SUBCARRIER_2 (1U << 0)
#define ISO15693_3_REQ_FLAG_DATA_RATE_LO (0U << 1)
@@ -176,6 +176,25 @@ Iso15693_3Error
return ret;
}
Iso15693_3Error iso15693_3_write_block_response_parse(const BitBuffer* buf) {
Iso15693_3Error ret = Iso15693_3ErrorNone;
do {
if(iso15693_3_error_response_parse(&ret, buf)) break;
typedef struct {
uint8_t flags;
} WriteBlockResponseLayout;
if(bit_buffer_get_size_bytes(buf) != sizeof(WriteBlockResponseLayout)) {
ret = Iso15693_3ErrorUnexpectedResponse;
break;
}
} while(false);
return ret;
}
Iso15693_3Error iso15693_3_get_block_security_response_parse(
uint8_t* data,
uint16_t block_count,
@@ -28,6 +28,8 @@ Iso15693_3Error
Iso15693_3Error
iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf);
Iso15693_3Error iso15693_3_write_block_response_parse(const BitBuffer* buf);
Iso15693_3Error iso15693_3_get_block_security_response_parse(
uint8_t* data,
uint16_t block_count,
@@ -13,6 +13,14 @@ extern "C" {
*/
typedef struct Iso15693_3Poller Iso15693_3Poller;
/**
* @brief Get the Iso15693_3 data accumulated by the poller.
*
* @param[in] instance pointer to the Iso15693_3Poller instance.
* @return pointer to the Iso15693_3Data structure filled during activation.
*/
const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance);
/**
* @brief Enumeration of possible Iso15693_3 poller event types.
*/
@@ -144,6 +152,40 @@ Iso15693_3Error iso15693_3_poller_get_blocks_security(
uint8_t* data,
uint16_t block_count);
/**
* @brief Write a single Iso15693_3 block.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_number block number to write.
* @param[in] block_size size of the block in bytes.
* @return Iso15693_3ErrorNone on success, an error code on failure.
*/
Iso15693_3Error iso15693_3_poller_write_block(
Iso15693_3Poller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size);
/**
* @brief Write multiple consecutive Iso15693_3 blocks.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_count number of blocks to write.
* @param[in] block_size size of each block in bytes.
* @return Iso15693_3ErrorNone on success, an error code on failure.
*/
Iso15693_3Error iso15693_3_poller_write_blocks(
Iso15693_3Poller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size);
#ifdef __cplusplus
}
#endif
@@ -234,6 +234,57 @@ Iso15693_3Error iso15693_3_poller_read_blocks(
return ret;
}
Iso15693_3Error iso15693_3_poller_write_block(
Iso15693_3Poller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
bit_buffer_reset(instance->tx_buffer);
bit_buffer_reset(instance->rx_buffer);
bit_buffer_append_byte(
instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI);
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_WRITE_BLOCK);
bit_buffer_append_byte(instance->tx_buffer, block_number);
bit_buffer_append_bytes(instance->tx_buffer, data, block_size);
Iso15693_3Error ret;
do {
ret = iso15693_3_poller_send_frame(
instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_WRITE_POLL_FC);
if(ret != Iso15693_3ErrorNone) break;
ret = iso15693_3_write_block_response_parse(instance->rx_buffer);
} while(false);
return ret;
}
Iso15693_3Error iso15693_3_poller_write_blocks(
Iso15693_3Poller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
furi_assert(block_count);
furi_assert(block_size);
Iso15693_3Error ret = Iso15693_3ErrorNone;
for(uint32_t i = 0; i < block_count; ++i) {
ret = iso15693_3_poller_write_block(
instance, &data[block_size * i], (uint8_t)i, block_size);
if(ret != Iso15693_3ErrorNone) break;
}
return ret;
}
Iso15693_3Error iso15693_3_poller_get_blocks_security(
Iso15693_3Poller* instance,
uint8_t* data,
@@ -33,8 +33,6 @@ struct Iso15693_3Poller {
void* context;
};
const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance);
#ifdef __cplusplus
}
#endif
+34
View File
@@ -118,6 +118,40 @@ SlixError slix_poller_set_password(
SlixPassword password,
SlixRandomNumber random_number);
/**
* @brief Write a single block to a Slix card.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_number block number to write.
* @param[in] block_size size of the block in bytes.
* @return SlixErrorNone on success, an error code on failure.
*/
SlixError slix_poller_write_block(
SlixPoller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size);
/**
* @brief Write multiple consecutive blocks to a Slix card.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_count number of blocks to write.
* @param[in] block_size size of each block in bytes.
* @return SlixErrorNone on success, an error code on failure.
*/
SlixError slix_poller_write_blocks(
SlixPoller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size);
#ifdef __cplusplus
}
#endif
+28
View File
@@ -128,3 +128,31 @@ SlixError slix_poller_set_password(
return error;
}
SlixError slix_poller_write_block(
SlixPoller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
const Iso15693_3Error error =
iso15693_3_poller_write_block(instance->iso15693_3_poller, data, block_number, block_size);
return slix_process_iso15693_3_error(error);
}
SlixError slix_poller_write_blocks(
SlixPoller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
furi_assert(block_count);
furi_assert(block_size);
const Iso15693_3Error error =
iso15693_3_poller_write_blocks(instance->iso15693_3_poller, data, block_count, block_size);
return slix_process_iso15693_3_error(error);
}
+6 -1
View File
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,87.6,,
Version,+,87.7,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/applications.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
@@ -2360,11 +2360,14 @@ Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Dat
Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t"
Function,+,iso15693_3_poller_activate,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3Data*"
Function,+,iso15693_3_poller_get_blocks_security,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t"
Function,+,iso15693_3_poller_get_data,const Iso15693_3Data*,Iso15693_3Poller*
Function,+,iso15693_3_poller_get_system_info,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3SystemInfo*"
Function,+,iso15693_3_poller_inventory,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*"
Function,+,iso15693_3_poller_read_block,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t"
Function,+,iso15693_3_poller_read_blocks,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t"
Function,+,iso15693_3_poller_send_frame,Iso15693_3Error,"Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t"
Function,+,iso15693_3_poller_write_block,Iso15693_3Error,"Iso15693_3Poller*, const uint8_t*, uint8_t, uint8_t"
Function,+,iso15693_3_poller_write_blocks,Iso15693_3Error,"Iso15693_3Poller*, const uint8_t*, uint16_t, uint8_t"
Function,+,iso15693_3_reset,void,Iso15693_3Data*
Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*"
Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t"
@@ -3397,6 +3400,8 @@ Function,+,slix_poller_get_random_number,SlixError,"SlixPoller*, SlixRandomNumbe
Function,+,slix_poller_read_signature,SlixError,"SlixPoller*, SlixSignature*"
Function,+,slix_poller_send_frame,SlixError,"SlixPoller*, const BitBuffer*, BitBuffer*, uint32_t"
Function,+,slix_poller_set_password,SlixError,"SlixPoller*, SlixPasswordType, SlixPassword, SlixRandomNumber"
Function,+,slix_poller_write_block,SlixError,"SlixPoller*, const uint8_t*, uint8_t, uint8_t"
Function,+,slix_poller_write_blocks,SlixError,"SlixPoller*, const uint8_t*, uint16_t, uint8_t"
Function,+,slix_reset,void,SlixData*
Function,+,slix_save,_Bool,"const SlixData*, FlipperFormat*"
Function,+,slix_set_uid,_Bool,"SlixData*, const uint8_t*, size_t"
1 entry status name type params
2 Version + 87.6 87.7
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/applications.h
5 Header + applications/services/bt/bt_service/bt.h
2360 Function + iso15693_3_load _Bool Iso15693_3Data*, FlipperFormat*, uint32_t
2361 Function + iso15693_3_poller_activate Iso15693_3Error Iso15693_3Poller*, Iso15693_3Data*
2362 Function + iso15693_3_poller_get_blocks_security Iso15693_3Error Iso15693_3Poller*, uint8_t*, uint16_t
2363 Function + iso15693_3_poller_get_data const Iso15693_3Data* Iso15693_3Poller*
2364 Function + iso15693_3_poller_get_system_info Iso15693_3Error Iso15693_3Poller*, Iso15693_3SystemInfo*
2365 Function + iso15693_3_poller_inventory Iso15693_3Error Iso15693_3Poller*, uint8_t*
2366 Function + iso15693_3_poller_read_block Iso15693_3Error Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t
2367 Function + iso15693_3_poller_read_blocks Iso15693_3Error Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t
2368 Function + iso15693_3_poller_send_frame Iso15693_3Error Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t
2369 Function + iso15693_3_poller_write_block Iso15693_3Error Iso15693_3Poller*, const uint8_t*, uint8_t, uint8_t
2370 Function + iso15693_3_poller_write_blocks Iso15693_3Error Iso15693_3Poller*, const uint8_t*, uint16_t, uint8_t
2371 Function + iso15693_3_reset void Iso15693_3Data*
2372 Function + iso15693_3_save _Bool const Iso15693_3Data*, FlipperFormat*
2373 Function + iso15693_3_set_uid _Bool Iso15693_3Data*, const uint8_t*, size_t
3400 Function + slix_poller_read_signature SlixError SlixPoller*, SlixSignature*
3401 Function + slix_poller_send_frame SlixError SlixPoller*, const BitBuffer*, BitBuffer*, uint32_t
3402 Function + slix_poller_set_password SlixError SlixPoller*, SlixPasswordType, SlixPassword, SlixRandomNumber
3403 Function + slix_poller_write_block SlixError SlixPoller*, const uint8_t*, uint8_t, uint8_t
3404 Function + slix_poller_write_blocks SlixError SlixPoller*, const uint8_t*, uint16_t, uint8_t
3405 Function + slix_reset void SlixData*
3406 Function + slix_save _Bool const SlixData*, FlipperFormat*
3407 Function + slix_set_uid _Bool SlixData*, const uint8_t*, size_t