diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c index 66839aacc..dd724b13a 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -21,6 +21,7 @@ #include "mf_desfire/mf_desfire.h" #include "slix/slix.h" #include "st25tb/st25tb.h" +#include "type_4_tag/type_4_tag.h" /** * @brief Array of pointers to concrete protocol support implementations. @@ -43,5 +44,6 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, [NfcProtocolSlix] = &nfc_protocol_support_slix, [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, + [NfcProtocolType4Tag] = &nfc_protocol_support_type_4_tag, /* Add new protocol support implementations here */ }; diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..aefd095c1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c @@ -0,0 +1,220 @@ +#include "type_4_tag.h" +#include "type_4_tag_render.h" + +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +enum { + SubmenuIndexWrite = SubmenuIndexCommonMax, +}; + +enum { + NfcSceneMoreInfoStateASCII, + NfcSceneMoreInfoStateRawData, +}; + +static void nfc_scene_info_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_type_4_tag_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + furi_string_reset(instance->text_box_store); + uint32_t scene_state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo); + + if(scene_state == NfcSceneMoreInfoStateASCII) { + pretty_format_bytes_hex_canonical( + instance->text_box_store, + TYPE_4_TAG_RENDER_BYTES_PER_LINE, + PRETTY_FORMAT_FONT_MONOSPACE, + simple_array_cget_data(data->ndef_data), + simple_array_get_count(data->ndef_data)); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Raw Data", + nfc_protocol_support_common_widget_callback, + instance); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Info", + nfc_protocol_support_common_widget_callback, + instance); + } else if(scene_state == NfcSceneMoreInfoStateRawData) { + nfc_render_type_4_tag_dump(data, instance->text_box_store); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "ASCII", + nfc_protocol_support_common_widget_callback, + instance); + } +} + +static bool nfc_scene_more_info_on_event_type_4_tag(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) || + (event.type == SceneManagerEventTypeBack)) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII); + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData); + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + return consumed; +} + +static NfcCommand nfc_scene_read_poller_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + const Type4TagPollerEvent* type_4_tag_event = event.event_data; + + if(type_4_tag_event->type == Type4TagPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolType4Tag, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(type_4_tag_event->type == Type4TagPollerEventTypeReadFailed) { + command = NfcCommandReset; + } + + return command; +} + +static void nfc_scene_read_on_enter_type_4_tag(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_type_4_tag, instance); +} + +static void nfc_scene_read_and_saved_menu_on_enter_type_4_tag(NfcApp* instance) { + Submenu* submenu = instance->submenu; + + submenu_add_item( + submenu, + "Write (Not Implemented)", + SubmenuIndexWrite, + nfc_protocol_support_common_submenu_callback, + instance); +} + +static bool + nfc_scene_read_and_saved_menu_on_event_type_4_tag(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + UNUSED(instance); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + // TODO: Implement write + // scene_manager_next_scene(instance->scene_manager, NfcSceneType4TagWrite); + consumed = true; + } + } + return consumed; +} + +static void nfc_scene_read_success_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_type_4_tag_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_type_4_tag(NfcApp* instance) { + // TODO: Implement full emulation + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_type_4_tag = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_type_4_tag, + .on_event = nfc_scene_more_info_on_event_type_4_tag, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_scene_read_and_saved_menu_on_enter_type_4_tag, + .on_event = nfc_scene_read_and_saved_menu_on_event_type_4_tag, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_scene_read_and_saved_menu_on_enter_type_4_tag, + .on_event = nfc_scene_read_and_saved_menu_on_event_type_4_tag, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..e9d43a3b4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_type_4_tag; diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c new file mode 100644 index 000000000..bf85feb78 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c @@ -0,0 +1,50 @@ +#include "type_4_tag_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +#define TYPE_4_TAG_RENDER_MAX_RECORD_SIZE (256U) + +void nfc_render_type_4_tag_info( + const Type4TagData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(type_4_tag_get_base_data(data), str); + + furi_string_cat(str, "\n::::::::::::::::::[Tag Specs]::::::::::::::::::\n"); + furi_string_cat_printf( + str, "T4T Mapping Version: %u.%u\n", data->t4t_version.major, data->t4t_version.minor); + furi_string_cat_printf(str, "NDEF File ID: 0x%04X\n", data->ndef_file_id); + furi_string_cat_printf(str, "Max NDEF Size: %u\n", data->ndef_max_len); + furi_string_cat_printf( + str, "APDU Sizes: R:%u W:%u\n", data->chunk_max_read, data->chunk_max_write); + furi_string_cat_printf( + str, + "Read Lock: 0x%02X%s\n", + data->ndef_read_lock, + data->ndef_read_lock == 0 ? " (unlocked)" : ""); + furi_string_cat_printf( + str, + "Write Lock: 0x%02X%s\n", + data->ndef_write_lock, + data->ndef_write_lock == 0 ? " (unlocked)" : ""); + + furi_string_cat(str, ":::::::::::::::[Stored NDEF]:::::::::::::::\n"); + furi_string_cat_printf(str, "Current NDEF Size: %lu", simple_array_get_count(data->ndef_data)); + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(type_4_tag_get_base_data(data), str); +} + +void nfc_render_type_4_tag_dump(const Type4TagData* data, FuriString* str) { + furi_string_cat_printf(str, "\e*"); + const uint8_t* ndef_data = simple_array_cget_data(data->ndef_data); + size_t ndef_len = simple_array_get_count(data->ndef_data); + for(size_t i = 0; i < ndef_len; i += TYPE_4_TAG_RENDER_BYTES_PER_LINE) { + const uint8_t* line_data = &ndef_data[i]; + for(size_t j = 0; j < TYPE_4_TAG_RENDER_BYTES_PER_LINE; j += 2) { + furi_string_cat_printf(str, " %02X%02X", line_data[j], line_data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h new file mode 100644 index 000000000..91eaeb735 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +#define TYPE_4_TAG_RENDER_BYTES_PER_LINE (4) + +void nfc_render_type_4_tag_info( + const Type4TagData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_type_4_tag_dump(const Type4TagData* data, FuriString* str); diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 82b87ea2a..9aef970c8 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -26,6 +26,7 @@ env.Append( File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), File("protocols/felica/felica.h"), + File("protocols/type_4_tag/type_4_tag.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), @@ -38,6 +39,7 @@ env.Append( File("protocols/slix/slix_poller.h"), File("protocols/st25tb/st25tb_poller.h"), File("protocols/felica/felica_poller.h"), + File("protocols/type_4_tag/type_4_tag_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 6a145445c..df7ebde40 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -24,6 +24,7 @@ #include #include #include +#include /** * @brief List of registered NFC device implementations. @@ -44,5 +45,6 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, + [NfcProtocolType4Tag] = &nfc_device_type_4_tag, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 2a6167e9c..69320bd79 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -14,10 +14,11 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, [NfcProtocolIso14443_4b] = NULL, [NfcProtocolIso15693_3] = &nfc_listener_iso15693_3, + [NfcProtocolFelica] = &nfc_listener_felica, [NfcProtocolMfUltralight] = &mf_ultralight_listener, [NfcProtocolMfClassic] = &mf_classic_listener, [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, - [NfcProtocolFelica] = &nfc_listener_felica, + [NfcProtocolType4Tag] = NULL, }; diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index c007740b7..d11b74eb6 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -12,6 +12,7 @@ #include #include #include +#include const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, @@ -25,6 +26,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfPlus] = &mf_plus_poller, [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, - /* Add new pollers here */ [NfcProtocolSt25tb] = &nfc_poller_st25tb, + [NfcProtocolType4Tag] = &type_4_tag_poller, + /* Add new pollers here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 40201843e..fd64a7b0c 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -22,7 +22,9 @@ * | | | * ISO14443-4A Mf Ultralight Mf Classic * | - * Mf Desfire + * +-----+------+----------+ + * | | | + * Mf Desfire Type 4 Tag Mf Plus * ``` * * When implementing a new protocol, its place in the tree must be determined first. @@ -62,6 +64,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { NfcProtocolMfDesfire, NfcProtocolMfPlus, + NfcProtocolType4Tag, }; /** List of ISO115693-3 child protocols. */ @@ -153,6 +156,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolType4Tag] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index cf74972f7..db03c74e7 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -188,6 +188,7 @@ typedef enum { NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, + NfcProtocolType4Tag, /* Add new protocols here */ NfcProtocolNum, /**< Special value representing the number of available protocols. */ diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.c b/lib/nfc/protocols/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..ec268b618 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.c @@ -0,0 +1,158 @@ +#include "type_4_tag_i.h" + +#include + +#define TYPE_4_TAG_PROTOCOL_NAME "Type 4 Tag" + +const NfcDeviceBase nfc_device_type_4_tag = { + .protocol_name = TYPE_4_TAG_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)type_4_tag_alloc, + .free = (NfcDeviceFree)type_4_tag_free, + .reset = (NfcDeviceReset)type_4_tag_reset, + .copy = (NfcDeviceCopy)type_4_tag_copy, + .verify = (NfcDeviceVerify)type_4_tag_verify, + .load = (NfcDeviceLoad)type_4_tag_load, + .save = (NfcDeviceSave)type_4_tag_save, + .is_equal = (NfcDeviceEqual)type_4_tag_is_equal, + .get_name = (NfcDeviceGetName)type_4_tag_get_device_name, + .get_uid = (NfcDeviceGetUid)type_4_tag_get_uid, + .set_uid = (NfcDeviceSetUid)type_4_tag_set_uid, + .get_base_data = (NfcDeviceGetBaseData)type_4_tag_get_base_data, +}; + +Type4TagData* type_4_tag_alloc(void) { + Type4TagData* data = malloc(sizeof(Type4TagData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->ndef_data = simple_array_alloc(&simple_array_config_uint8_t); + return data; +} + +void type_4_tag_free(Type4TagData* data) { + furi_check(data); + + type_4_tag_reset(data); + simple_array_free(data->ndef_data); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void type_4_tag_reset(Type4TagData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + data->t4t_version.value = 0; + data->chunk_max_read = 0; + data->chunk_max_write = 0; + data->ndef_file_id = 0; + data->ndef_max_len = 0; + data->ndef_read_lock = 0; + data->ndef_write_lock = 0; + + simple_array_reset(data->ndef_data); +} + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + type_4_tag_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->t4t_version.value = other->t4t_version.value; + data->chunk_max_read = other->chunk_max_read; + data->chunk_max_write = other->chunk_max_write; + data->ndef_file_id = other->ndef_file_id; + data->ndef_max_len = other->ndef_max_len; + data->ndef_read_lock = other->ndef_read_lock; + data->ndef_write_lock = other->ndef_write_lock; + + simple_array_copy(data->ndef_data, other->ndef_data); +} + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, TYPE_4_TAG_PROTOCOL_NAME " specific data")) + break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + data->t4t_version.value == other->t4t_version.value && + data->chunk_max_read == other->chunk_max_read && + data->chunk_max_write == other->chunk_max_write && + data->ndef_file_id == other->ndef_file_id && + data->ndef_max_len == other->ndef_max_len && + data->ndef_read_lock == other->ndef_read_lock && + data->ndef_write_lock == other->ndef_write_lock && + simple_array_is_equal(data->ndef_data, other->ndef_data); +} + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return TYPE_4_TAG_PROTOCOL_NAME; +} + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len) { + furi_check(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.h b/lib/nfc/protocols/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..99f58b7cc --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagErrorNone, + Type4TagErrorNotPresent, + Type4TagErrorProtocol, + Type4TagErrorTimeout, +} Type4TagError; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + // Tag specific + union { + struct { + uint8_t minor : 4; + uint8_t major : 4; + }; + uint8_t value; + } t4t_version; + uint16_t chunk_max_read; + uint16_t chunk_max_write; + uint16_t ndef_file_id; + uint16_t ndef_max_len; + uint8_t ndef_read_lock; + uint8_t ndef_write_lock; + // Data contained, not tag specific + SimpleArray* ndef_data; +} Type4TagData; + +extern const NfcDeviceBase nfc_device_type_4_tag; + +// Virtual methods + +Type4TagData* type_4_tag_alloc(void); + +void type_4_tag_free(Type4TagData* data); + +void type_4_tag_reset(Type4TagData* data); + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other); + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type); + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version); + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff); + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other); + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type); + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len); + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c new file mode 100644 index 000000000..c2cbfd1af --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c @@ -0,0 +1,3 @@ +#include "type_4_tag_i.h" + +#define TAG "Type4Tag" diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h new file mode 100644 index 000000000..0dcece22b --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h @@ -0,0 +1,54 @@ +#pragma once + +#include "type_4_tag.h" + +#define TYPE_4_TAG_ISO_SELECT_CMD 0x00, 0xA4 +#define TYPE_4_TAG_ISO_SELECT_P1_BY_NAME (0x04) +#define TYPE_4_TAG_ISO_SELECT_P1_BY_ID (0x00) +#define TYPE_4_TAG_ISO_SELECT_P2_EMPTY (0x0C) +#define TYPE_4_TAG_ISO_SELECT_LE_EMPTY (0x00) + +#define TYPE_4_TAG_ISO_READ_CMD 0x00, 0xB0 +#define TYPE_4_TAG_ISO_READ_P1_EMPTY (0x00) +#define TYPE_4_TAG_ISO_READ_P2_BEGINNING (0x00) +#define TYPE_4_TAG_ISO_READ_LE_FULL (0x00) + +#define TYPE_4_TAG_ISO_STATUS_LEN (2) +#define TYPE_4_TAG_ISO_STATUS_SUCCESS 0x90, 0x00 +#define TYPE_4_TAG_ISO_RW_CHUNK_LEN (255) +#define TYPE_4_TAG_ISO_APP_NAME_LEN (7) +#define TYPE_4_TAG_ISO_APP_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 +#define TYPE_4_TAG_T4T_CC_FILE_ID_LEN (2) +#define TYPE_4_TAG_T4T_CC_FILE_ID 0xE1, 0x03 + +#define TYPE_4_TAG_T4T_CC_VNO 0x20 + +typedef enum FURI_PACKED { + Type4TagCcTlvTypeNdefFileCtrl = 0x04, + Type4TagCcTlvTypeProprietaryFileCtrl = 0x05, +} Type4TagCcTlvType; + +typedef struct FURI_PACKED { + uint16_t file_id; + uint16_t max_len; + uint8_t read_perm; + uint8_t write_perm; +} Type4TagCcTlvNdefFileCtrl; + +typedef union FURI_PACKED { + Type4TagCcTlvNdefFileCtrl ndef_file_ctrl; +} Type4TagCcTlvValue; + +typedef struct FURI_PACKED { + Type4TagCcTlvType type; + uint8_t len; + Type4TagCcTlvValue value; +} Type4TagCcTlv; + +typedef struct FURI_PACKED { + uint16_t len; + uint8_t t4t_vno; + uint16_t mle; + uint16_t mlc; + Type4TagCcTlv tlv[]; +} Type4TagCc; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c new file mode 100644 index 000000000..ee09d8a68 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c @@ -0,0 +1,187 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_i.h" + +#include + +#include + +#define TAG "Type4TagPoller" + +// Read returns 2 byte status trailer, write sends 5 byte command header +#define TYPE_4_TAG_BUF_SIZE (TYPE_4_TAG_ISO_RW_CHUNK_LEN + 5) + +typedef NfcCommand (*Type4TagPollerReadHandler)(Type4TagPoller* instance); + +const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Type4TagPoller* type_4_tag_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Type4TagPoller* instance = malloc(sizeof(Type4TagPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = type_4_tag_alloc(); + instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + + instance->type_4_tag_event.data = &instance->type_4_tag_event_data; + + instance->general_event.protocol = NfcProtocolType4Tag; + instance->general_event.event_data = &instance->type_4_tag_event; + instance->general_event.instance = instance; + + return instance; +} + +static void type_4_tag_poller_free(Type4TagPoller* instance) { + furi_assert(instance); + + type_4_tag_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand type_4_tag_poller_handler_idle(Type4TagPoller* instance) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = Type4TagPollerStateSelectApplication; + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_select_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_select_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Select application success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to select application"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = Type4TagPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read CC success"); + instance->state = Type4TagPollerStateReadNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to read CC"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = Type4TagPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read NDEF success"); + instance->state = Type4TagPollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read NDEF"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = Type4TagPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_fail(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Type4TagPollerStateIdle; + return command; +} + +static NfcCommand type_4_tag_poller_handler_read_success(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Read success."); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = Type4TagPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Type4TagPollerReadHandler type_4_tag_poller_read_handler[Type4TagPollerStateNum] = { + [Type4TagPollerStateIdle] = type_4_tag_poller_handler_idle, + [Type4TagPollerStateSelectApplication] = type_4_tag_poller_handler_select_app, + [Type4TagPollerStateReadCapabilityContainer] = type_4_tag_poller_handler_read_cc, + [Type4TagPollerStateReadNdefMessage] = type_4_tag_poller_handler_read_ndef, + [Type4TagPollerStateReadFailed] = type_4_tag_poller_handler_read_fail, + [Type4TagPollerStateReadSuccess] = type_4_tag_poller_handler_read_success, +}; + +static void type_4_tag_poller_set_callback( + Type4TagPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand type_4_tag_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = type_4_tag_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->type_4_tag_event.type = Type4TagPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool type_4_tag_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + Type4TagError error = type_4_tag_poller_select_app(instance); + if(error == Type4TagErrorNone) { + protocol_detected = true; + } + } + + return protocol_detected; +} + +const NfcPollerBase type_4_tag_poller = { + .alloc = (NfcPollerAlloc)type_4_tag_poller_alloc, + .free = (NfcPollerFree)type_4_tag_poller_free, + .set_callback = (NfcPollerSetCallback)type_4_tag_poller_set_callback, + .run = (NfcPollerRun)type_4_tag_poller_run, + .detect = (NfcPollerDetect)type_4_tag_poller_detect, + .get_data = (NfcPollerGetData)type_4_tag_poller_get_data, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h new file mode 100644 index 000000000..c3ba91c32 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h @@ -0,0 +1,43 @@ +#pragma once + +#include "type_4_tag.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type4TagPoller opaque type definition. + */ +typedef struct Type4TagPoller Type4TagPoller; + +/** + * @brief Enumeration of possible Type4Tag poller event types. + */ +typedef enum { + Type4TagPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Type4TagPollerEventTypeReadFailed, /**< Poller failed to read card. */ +} Type4TagPollerEventType; + +/** + * @brief Type4Tag poller event data. + */ +typedef union { + Type4TagError error; /**< Error code indicating card reading fail reason. */ +} Type4TagPollerEventData; + +/** + * @brief Type4Tag poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Type4TagPollerEventType type; /**< Type of emmitted event. */ + Type4TagPollerEventData* data; /**< Pointer to event specific data. */ +} Type4TagPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h new file mode 100644 index 000000000..fe71b560a --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase type_4_tag_poller; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c new file mode 100644 index 000000000..5d8c56d40 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c @@ -0,0 +1,265 @@ +#include "type_4_tag_poller_i.h" + +#include +#include + +#include "type_4_tag_i.h" + +#define TAG "Type4TagPoller" + +Type4TagError type_4_tag_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return Type4TagErrorNone; + case Iso14443_4aErrorNotPresent: + return Type4TagErrorNotPresent; + case Iso14443_4aErrorTimeout: + return Type4TagErrorTimeout; + default: + return Type4TagErrorProtocol; + } +} + +Type4TagError type_4_tag_apdu_trx(Type4TagPoller* instance, BitBuffer* tx_buf, BitBuffer* rx_buf) { + furi_check(instance); + + bit_buffer_reset(rx_buf); + + Iso14443_4aError iso14443_4a_error = + iso14443_4a_poller_send_block(instance->iso14443_4a_poller, tx_buf, rx_buf); + + bit_buffer_reset(tx_buf); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return type_4_tag_process_error(iso14443_4a_error); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buf); + if(response_len < TYPE_4_TAG_ISO_STATUS_LEN) { + return Type4TagErrorProtocol; + } + + const uint8_t success[TYPE_4_TAG_ISO_STATUS_LEN] = {TYPE_4_TAG_ISO_STATUS_SUCCESS}; + uint8_t status[TYPE_4_TAG_ISO_STATUS_LEN] = { + bit_buffer_get_byte(rx_buf, response_len - 2), + bit_buffer_get_byte(rx_buf, response_len - 1), + }; + bit_buffer_set_size_bytes(rx_buf, response_len - 2); + + if(memcmp(status, success, sizeof(status)) == 0) { + return Type4TagErrorNone; + } else { + FURI_LOG_E(TAG, "APDU failed: 0x%02X%02X", status[0], status[1]); + return Type4TagErrorProtocol; + } +} + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance) { + furi_check(instance); + + FURI_LOG_D(TAG, "Select application"); + const uint8_t type_4_tag_select_app_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_NAME, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + TYPE_4_TAG_ISO_APP_NAME_LEN, + TYPE_4_TAG_ISO_APP_NAME, + TYPE_4_TAG_ISO_SELECT_LE_EMPTY, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_select_app_apdu, sizeof(type_4_tag_select_app_apdu)); + + return type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); +} + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select CC"); + const uint8_t type_4_tag_select_cc_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_ID, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + TYPE_4_TAG_T4T_CC_FILE_ID_LEN, + TYPE_4_TAG_T4T_CC_FILE_ID, + TYPE_4_TAG_ISO_SELECT_LE_EMPTY, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_select_cc_apdu, sizeof(type_4_tag_select_cc_apdu)); + + error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read CC"); + const uint8_t type_4_tag_read_cc_apdu[] = { + TYPE_4_TAG_ISO_READ_CMD, + TYPE_4_TAG_ISO_READ_P1_EMPTY, + TYPE_4_TAG_ISO_READ_P2_BEGINNING, + TYPE_4_TAG_ISO_READ_LE_FULL, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_read_cc_apdu, sizeof(type_4_tag_read_cc_apdu)); + + error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) break; + + const Type4TagCc* cc = (const Type4TagCc*)bit_buffer_get_data(instance->rx_buffer); + if(cc->t4t_vno != TYPE_4_TAG_T4T_CC_VNO) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + error = Type4TagErrorProtocol; + break; + } + + const Type4TagCcTlv* tlv = cc->tlv; + const Type4TagCcTlvNdefFileCtrl* ndef_file_ctrl = NULL; + while((void*)tlv < (void*)cc + cc->len) { + if(tlv->type == Type4TagCcTlvTypeNdefFileCtrl) { + ndef_file_ctrl = &tlv->value.ndef_file_ctrl; + break; + } + + if(tlv->len < 0xFF) { + tlv = (void*)&tlv->value + tlv->len; + } else { + uint16_t len = bit_lib_bytes_to_num_be((void*)&tlv->len + 1, sizeof(uint16_t)); + tlv = (void*)&tlv->value + sizeof(len) + len; + } + } + if(!ndef_file_ctrl) { + FURI_LOG_E(TAG, "No NDEF file ctrl TLV"); + error = Type4TagErrorProtocol; + break; + } + + instance->data->t4t_version.value = cc->t4t_vno; + instance->data->chunk_max_read = bit_lib_bytes_to_num_be((void*)&cc->mle, sizeof(cc->mle)); + instance->data->chunk_max_write = + bit_lib_bytes_to_num_be((void*)&cc->mlc, sizeof(cc->mlc)); + instance->data->ndef_file_id = bit_lib_bytes_to_num_be( + (void*)&ndef_file_ctrl->file_id, sizeof(ndef_file_ctrl->file_id)); + instance->data->ndef_max_len = + bit_lib_bytes_to_num_be( + (void*)&ndef_file_ctrl->max_len, sizeof(ndef_file_ctrl->max_len)) - + sizeof(uint16_t); + instance->data->ndef_read_lock = ndef_file_ctrl->read_perm; + instance->data->ndef_write_lock = ndef_file_ctrl->write_perm; + + FURI_LOG_D(TAG, "Detected NDEF file ID 0x%04X", instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + const uint8_t type_4_tag_select_ndef_apdu_1[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_ID, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + sizeof(instance->data->ndef_file_id), + }; + uint8_t ndef_file_id_be[sizeof(instance->data->ndef_file_id)]; + bit_lib_num_to_bytes_be( + instance->data->ndef_file_id, sizeof(instance->data->ndef_file_id), ndef_file_id_be); + const uint8_t type_4_tag_select_ndef_apdu_2[] = { + TYPE_4_TAG_ISO_SELECT_LE_EMPTY, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_select_ndef_apdu_1, + sizeof(type_4_tag_select_ndef_apdu_1)); + bit_buffer_append_bytes(instance->tx_buffer, ndef_file_id_be, sizeof(ndef_file_id_be)); + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_select_ndef_apdu_2, + sizeof(type_4_tag_select_ndef_apdu_2)); + + error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read NDEF len"); + uint16_t ndef_len; + const uint8_t type_4_tag_read_ndef_len_apdu[] = { + TYPE_4_TAG_ISO_READ_CMD, + TYPE_4_TAG_ISO_READ_P1_EMPTY, + TYPE_4_TAG_ISO_READ_P2_BEGINNING, + sizeof(ndef_len), + }; + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_read_ndef_len_apdu, + sizeof(type_4_tag_read_ndef_len_apdu)); + + error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) break; + + ndef_len = + bit_lib_bytes_to_num_be(bit_buffer_get_data(instance->rx_buffer), sizeof(ndef_len)); + simple_array_init(instance->data->ndef_data, ndef_len); + if(ndef_len > 510) { + // Both size and offset for READ BINARY are 1 byte uint + // So the furthest we can read is 255 bytes at offset 255 + // AKA 2 * 255 byte chunks = 510 bytes + // TODO: Surely there has to be another way? + FURI_LOG_E(TAG, "NDEF file too long: %zu bytes", ndef_len); + } + + FURI_LOG_D(TAG, "Read NDEF"); + const uint8_t type_4_tag_read_ndef_apdu_1[] = { + TYPE_4_TAG_ISO_READ_CMD, + TYPE_4_TAG_ISO_READ_P1_EMPTY, + }; + + uint16_t ndef_pos = 0; + uint8_t* ndef_data = simple_array_get_data(instance->data->ndef_data); + uint8_t chunk_max = MIN(instance->data->chunk_max_read, TYPE_4_TAG_ISO_RW_CHUNK_LEN); + while(ndef_len > 0) { + uint8_t chunk_len = MIN(ndef_len, chunk_max); + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_read_ndef_apdu_1, + sizeof(type_4_tag_read_ndef_apdu_1)); + bit_buffer_append_byte(instance->tx_buffer, (uint8_t)ndef_pos); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + + error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) break; + if(bit_buffer_get_size_bytes(instance->rx_buffer) != chunk_len) { + FURI_LOG_E( + TAG, + "Wrong NDEF chunk len: %zu != %zu", + bit_buffer_get_size_bytes(instance->rx_buffer), + ndef_len); + error = Type4TagErrorProtocol; + break; + } + memcpy(&ndef_data[ndef_pos], bit_buffer_get_data(instance->rx_buffer), chunk_len); + + ndef_pos += chunk_len; + ndef_len -= chunk_len; + } + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, + "Read NDEF file 0x%04X of %lu bytes", + instance->data->ndef_file_id, + simple_array_get_count(instance->data->ndef_data)); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h new file mode 100644 index 000000000..c74135040 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h @@ -0,0 +1,48 @@ +#pragma once + +#include "type_4_tag_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagPollerStateIdle, + Type4TagPollerStateSelectApplication, + Type4TagPollerStateReadCapabilityContainer, + Type4TagPollerStateReadNdefMessage, + Type4TagPollerStateReadFailed, + Type4TagPollerStateReadSuccess, + + Type4TagPollerStateNum, +} Type4TagPollerState; + +struct Type4TagPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Type4TagPollerState state; + Type4TagError error; + Type4TagData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Type4TagPollerEventData type_4_tag_event_data; + Type4TagPollerEvent type_4_tag_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Type4TagError type_4_tag_process_error(Iso14443_4aError error); + +const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1168a6eea..9483b1719 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -169,6 +169,8 @@ Header,+,lib/nfc/protocols/slix/slix_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag_poller.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -3625,6 +3627,18 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double +Function,+,type_4_tag_alloc,Type4TagData*, +Function,+,type_4_tag_copy,void,"Type4TagData*, const Type4TagData*" +Function,+,type_4_tag_free,void,Type4TagData* +Function,+,type_4_tag_get_base_data,Iso14443_4aData*,const Type4TagData* +Function,+,type_4_tag_get_device_name,const char*,"const Type4TagData*, NfcDeviceNameType" +Function,+,type_4_tag_get_uid,const uint8_t*,"const Type4TagData*, size_t*" +Function,+,type_4_tag_is_equal,_Bool,"const Type4TagData*, const Type4TagData*" +Function,+,type_4_tag_load,_Bool,"Type4TagData*, FlipperFormat*, uint32_t" +Function,+,type_4_tag_reset,void,Type4TagData* +Function,+,type_4_tag_save,_Bool,"const Type4TagData*, FlipperFormat*" +Function,+,type_4_tag_set_uid,_Bool,"Type4TagData*, const uint8_t*, size_t" +Function,+,type_4_tag_verify,_Bool,"Type4TagData*, const FuriString*" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -4012,6 +4026,7 @@ Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_plus,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, Variable,-,nfc_device_st25tb,const NfcDeviceBase, +Variable,-,nfc_device_type_4_tag,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence,