From b9fa2943ed782e29e7c0cd748bcdf865d9d920a7 Mon Sep 17 00:00:00 2001 From: DoniyorI Date: Tue, 31 Mar 2026 22:05:59 -0400 Subject: [PATCH] nfc: add ISO15693-3 and SLIX write-back support Add Write Single Block poller functions for ISO 15693-3 and SLIX/SLIX2, enable the Write scene for both protocols, and skip blocks that already contain the correct data to minimise RF session time and avoid timeouts on large tags (e.g. 80-block ICODE SLIX2). Tested on NXP ICODE SLIX2 (UID E0 04 01 09 05 05 09 37, 80 x 4 byte blocks). --- .../protocol_support/iso15693_3/iso15693_3.c | 70 +++++++++++- .../nfc/helpers/protocol_support/slix/slix.c | 105 +++++++++++++++++- lib/nfc/protocols/iso15693_3/iso15693_3.h | 5 + lib/nfc/protocols/iso15693_3/iso15693_3_i.c | 19 ++++ lib/nfc/protocols/iso15693_3/iso15693_3_i.h | 2 + .../protocols/iso15693_3/iso15693_3_poller.h | 42 +++++++ .../iso15693_3/iso15693_3_poller_i.c | 51 +++++++++ .../iso15693_3/iso15693_3_poller_i.h | 2 - lib/nfc/protocols/slix/slix_poller.h | 34 ++++++ lib/nfc/protocols/slix/slix_poller_i.c | 28 +++++ targets/f7/api_symbols.csv | 7 +- 11 files changed, 358 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index 4c69fbe81..d68b97dc0 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -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, }, }; diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 32094140d..895c1f2b5 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -9,6 +9,10 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include + +#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, }, }; diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.h b/lib/nfc/protocols/iso15693_3/iso15693_3.h index fdff7112b..d727429bd 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.h @@ -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) diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c index 3d8d95c3a..b3f68e0b5 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c @@ -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, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h index 253bda7f5..955b67409 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h @@ -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, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h index efe8337af..0f52012e9 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -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 diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index bc677ce67..a8a8dba87 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -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, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h index 346d0d724..1a0f48139 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h @@ -33,8 +33,6 @@ struct Iso15693_3Poller { void* context; }; -const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index e78f7882a..e0fee1a6a 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -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 diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index ee6912cc4..7759e1591 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -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); +} diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index bb03dcbb6..bccd2c381 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -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"