Merge remote-tracking branch 'fork-ofw/feat/nfc-type-4-final' into mntm-dev --nobuild

This commit is contained in:
WillyJL
2025-06-29 02:40:47 +01:00
100 changed files with 4887 additions and 1081 deletions
+20 -1
View File
@@ -13,8 +13,27 @@
- Sub-GHz: Weather Station (by @Skorpionm)
- Sub-GHz: POCSAG Pager (by @xMasterX)
- Sub-GHz: TPMS Reader (by @wosk)
- NFC: Ventra ULEV1 parser (by @hazardousvoltage)
- NFC:
- NFC Type 4 support + many other improvements (by @WillyJL)
- New Type 4 Tag (NDEF on NTAG4xx / MIFARE DESFire) protocol, full support
- New NTAG4xx (NTAG413 DNA / NTAG424 DNA) protocol, only detection and basic info support
- NDEF parsing plugin supports Type 4 Tag protocol
- Show more version info for MIFARE Plus cards
- Improve detection/verification of MIFARE DESFire and MIFARE Plus SE
- Improve navigation for MIFARE Classic Update from / Write to Initial Card
- Refactor Write code for MIFARE Ultralight/Classic in NFC app helpers
- Cleanup event handling in NFC app
- NFC app uses a bit less RAM because of previous 2 points
- Refactor NXP Native Commands to share between protocols (used by MIFARE DESFire, MIFARE Plus, NTAG4xx)
- MIFARE DESFire poller API can now switch between native and ISO7816-wrapped commands
- Expand ISO14443-4A API with listener (emulation) support for sending responses to reader (except I-block chaining)
- Exposed some APIs for apps to use that were meant to be public:
- ISO14443-3A listener (emulation)
- ISO15693-3 device (data), poller (reading), listener (emulation)
- Cleanup/reorder protocol definitions for tidyness
- Ventra ULEV1 parser (by @hazardousvoltage)
- Infrared: "Decode only" mode to ignore RAW signals, make buttons in learn scene more intuitive (by @WillyJL)
- GUI: Added `submenu_remove_item()` to API, was needed for NFC Type 4 related changes (by @WillyJL)
- UL: Sub-GHz: Add keeloq ironlogic aka il100 smart clone cloners support (by @xMasterX & Vitaly)
- UL: iButton: Add TM01x Dallas write support (by @Leptopt1los)
- UL: Display: Backlight option "Always ON" (by @Dmitry422)
+82 -44
View File
@@ -21,31 +21,6 @@ App(
# Protocol support plugins
App(
appid="nfc_emv",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_emv_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/emv/*.c",
],
fal_embedded=True,
)
App(
appid="nfc_felica",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_felica_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/felica/*.c",
"helpers/felica_*.c",
],
fal_embedded=True,
)
App(
appid="nfc_iso14443_3a",
targets=["f7"],
@@ -108,6 +83,34 @@ App(
fal_embedded=True,
)
App(
appid="nfc_felica",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_felica_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/felica/*.c",
"helpers/felica_*.c",
],
fal_embedded=True,
)
App(
appid="nfc_mf_ultralight",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_mf_ultralight_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/mf_ultralight/*.c",
"helpers/protocol_support/iso14443_3a/*.c",
"helpers/mf_ultralight_*.c",
],
fap_libs=["mbedtls"],
fal_embedded=True,
)
App(
appid="nfc_mf_classic",
targets=["f7"],
@@ -122,20 +125,6 @@ App(
fal_embedded=True,
)
App(
appid="nfc_mf_desfire",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_mf_desfire_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/mf_desfire/*.c",
"helpers/protocol_support/iso14443_4a/*.c",
"helpers/protocol_support/iso14443_3a/*.c",
],
fal_embedded=True,
)
App(
appid="nfc_mf_plus",
targets=["f7"],
@@ -151,17 +140,16 @@ App(
)
App(
appid="nfc_mf_ultralight",
appid="nfc_mf_desfire",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_mf_ultralight_ep",
entry_point="nfc_mf_desfire_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/mf_ultralight/*.c",
"helpers/protocol_support/mf_desfire/*.c",
"helpers/protocol_support/iso14443_4a/*.c",
"helpers/protocol_support/iso14443_3a/*.c",
"helpers/mf_ultralight_*.c",
],
fap_libs=["mbedtls"],
fal_embedded=True,
)
@@ -190,6 +178,46 @@ App(
fal_embedded=True,
)
App(
appid="nfc_ntag4xx",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_ntag4xx_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/ntag4xx/*.c",
"helpers/protocol_support/iso14443_4a/*.c",
"helpers/protocol_support/iso14443_3a/*.c",
],
fal_embedded=True,
)
App(
appid="nfc_type_4_tag",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_type_4_tag_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/type_4_tag/*.c",
"helpers/protocol_support/iso14443_4a/*.c",
"helpers/protocol_support/iso14443_3a/*.c",
],
fal_embedded=True,
)
App(
appid="nfc_emv",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_emv_ep",
requires=["nfc"],
sources=[
"helpers/protocol_support/emv/*.c",
],
fal_embedded=True,
)
# Parser plugins
App(
@@ -456,6 +484,16 @@ App(
sources=["plugins/supported_cards/ndef.c"],
)
App(
appid="ndef_t4t_parser",
apptype=FlipperAppType.PLUGIN,
cdefines=[("NDEF_PROTO", "NDEF_PROTO_T4T")],
entry_point="ndef_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/ndef.c"],
)
App(
appid="itso_parser",
apptype=FlipperAppType.PLUGIN,
@@ -13,6 +13,7 @@ typedef enum {
NfcCustomEventCardLost,
NfcCustomEventViewExit,
NfcCustomEventRetry,
NfcCustomEventWorkerExit,
NfcCustomEventWorkerUpdate,
NfcCustomEventWrongCard,
@@ -128,6 +128,11 @@ const NfcProtocolSupportBase nfc_protocol_support_emv = {
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(emv, NfcProtocolEmv);
@@ -133,15 +133,6 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) {
furi_string_free(temp_str);
}
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
return false;
}
static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) {
const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica);
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data);
@@ -201,7 +192,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = {
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_saved_menu_on_event_felica,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
@@ -213,6 +204,11 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = {
.on_enter = nfc_scene_emulate_on_enter_felica,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(felica, NfcProtocolFelica);
@@ -67,21 +67,22 @@ static NfcCommand
furi_assert(event.protocol == NfcProtocolIso14443_3a);
furi_assert(event.event_data);
NfcApp* nfc = context;
NfcApp* instance = context;
Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data;
if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) {
if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(nfc->text_box_store, "R:");
if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(instance->text_box_store, "R:");
for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_3a_event->data->buffer);
i++) {
furi_string_cat_printf(
nfc->text_box_store,
instance->text_box_store,
" %02X",
bit_buffer_get_byte(iso14443_3a_event->data->buffer, i));
}
furi_string_push_back(nfc->text_box_store, '\n');
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
furi_string_push_back(instance->text_box_store, '\n');
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventListenerUpdate);
}
}
@@ -97,15 +98,6 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}
return false;
}
const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = {
.features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid,
@@ -122,7 +114,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = {
.scene_read_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_read_menu_on_event_iso14443_3a,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_success =
{
@@ -144,6 +136,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = {
.on_enter = nfc_scene_emulate_on_enter_iso14443_3a,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_3a, NfcProtocolIso14443_3a);
@@ -60,19 +60,6 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) {
furi_string_free(temp_str);
}
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
return false;
}
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) {
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
}
const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = {
.features = NfcProtocolFeatureNone,
@@ -99,7 +86,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = {
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_saved_menu_on_event_iso14443_3b,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
@@ -111,6 +98,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = {
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_3b, NfcProtocolIso14443_3b);
@@ -1,7 +0,0 @@
#pragma once
#include <nfc/protocols/iso14443_3b/iso14443_3b.h>
#include "iso14443_3b.h"
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event);
@@ -70,21 +70,22 @@ NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event
furi_assert(event.protocol == NfcProtocolIso14443_4a);
furi_assert(event.event_data);
NfcApp* nfc = context;
NfcApp* instance = context;
Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data;
if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) {
if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(nfc->text_box_store, "R:");
if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(instance->text_box_store, "R:");
for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_4a_event->data->buffer);
i++) {
furi_string_cat_printf(
nfc->text_box_store,
instance->text_box_store,
" %02X",
bit_buffer_get_byte(iso14443_4a_event->data->buffer, i));
}
furi_string_push_back(nfc->text_box_store, '\n');
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
furi_string_push_back(instance->text_box_store, '\n');
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventListenerUpdate);
}
}
@@ -100,15 +101,6 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}
return false;
}
const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = {
.features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid,
@@ -125,7 +117,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = {
.scene_read_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_read_menu_on_event_iso14443_4a,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_success =
{
@@ -147,6 +139,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = {
.on_enter = nfc_scene_emulate_on_enter_iso14443_4a,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_4a, NfcProtocolIso14443_4a);
@@ -7,7 +7,6 @@
#include "../nfc_protocol_support_common.h"
#include "../nfc_protocol_support_gui_common.h"
#include "../iso14443_3b/iso14443_3b_i.h"
static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
@@ -61,23 +60,6 @@ static void nfc_scene_read_success_on_enter_iso14443_4b(NfcApp* instance) {
furi_string_free(temp_str);
}
static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) {
UNUSED(instance);
}
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
return true;
}
return false;
}
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
}
const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = {
.features = NfcProtocolFeatureNone,
@@ -94,7 +76,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = {
.scene_read_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_read_menu_on_event_iso14443_4b,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_success =
{
@@ -103,8 +85,8 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = {
},
.scene_saved_menu =
{
.on_enter = nfc_scene_saved_menu_on_enter_iso14443_4b,
.on_event = nfc_scene_saved_menu_on_event_iso14443_4b,
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
@@ -116,6 +98,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = {
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_4b, NfcProtocolIso14443_4b);
@@ -80,20 +80,21 @@ static NfcCommand
furi_assert(event.protocol == NfcProtocolIso15693_3);
furi_assert(event.event_data);
NfcApp* nfc = context;
NfcApp* instance = context;
Iso15693_3ListenerEvent* iso15693_3_event = event.event_data;
if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) {
if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(nfc->text_box_store, "R:");
if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(instance->text_box_store, "R:");
for(size_t i = 0; i < bit_buffer_get_size_bytes(iso15693_3_event->data->buffer); i++) {
furi_string_cat_printf(
nfc->text_box_store,
instance->text_box_store,
" %02X",
bit_buffer_get_byte(iso15693_3_event->data->buffer, i));
}
furi_string_push_back(nfc->text_box_store, '\n');
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
furi_string_push_back(instance->text_box_store, '\n');
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventListenerUpdate);
}
}
@@ -108,15 +109,6 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
}
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
return false;
}
const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid |
NfcProtocolFeatureMoreInfo,
@@ -149,7 +141,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_saved_menu_on_event_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
@@ -161,6 +153,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
.on_enter = nfc_scene_emulate_on_enter_iso15693_3,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(iso15693_3, NfcProtocolIso15693_3);
@@ -12,10 +12,9 @@
enum {
SubmenuIndexDetectReader = SubmenuIndexCommonMax,
SubmenuIndexWrite,
SubmenuIndexUpdate,
SubmenuIndexDictAttack,
SubmenuIndexCrackNonces,
SubmenuIndexUpdate,
};
static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
@@ -115,6 +114,9 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
Submenu* submenu = instance->submenu;
const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
// Doesn't make sense to show "Write to Initial Card" right after reading
submenu_remove_item(submenu, SubmenuIndexCommonWrite);
if(!mf_classic_is_card_read(data)) {
submenu_add_item(
submenu,
@@ -160,6 +162,8 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) {
Submenu* submenu = instance->submenu;
const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
submenu_change_item_label(submenu, SubmenuIndexCommonWrite, "Write to Initial Card");
if(!mf_classic_is_card_read(data)) {
submenu_add_item(
submenu,
@@ -175,12 +179,6 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) {
nfc_protocol_support_common_submenu_callback,
instance);
}
submenu_add_item(
submenu,
"Write to Initial Card",
SubmenuIndexWrite,
nfc_protocol_support_common_submenu_callback,
instance);
submenu_add_item(
submenu,
@@ -215,9 +213,6 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
}
consumed = true;
} else if(event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
consumed = true;
} else if(event.event == SubmenuIndexCrackNonces) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces);
@@ -236,18 +231,15 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana
if(event.event == SubmenuIndexDetectReader) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
consumed = true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
consumed = true;
} else if(event.event == SubmenuIndexUpdate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
consumed = true;
} else if(event.event == SubmenuIndexDictAttack) {
if(!scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneMfClassicDictAttack)) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
}
consumed = true;
} else if(event.event == SubmenuIndexUpdate) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
consumed = true;
}
}
@@ -267,8 +259,71 @@ static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManag
return consumed;
}
static NfcCommand
nfc_scene_write_poller_callback_mf_classic(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfClassic);
NfcApp* instance = context;
MfClassicPollerEvent* mfc_event = event.event_data;
NfcCommand command = NfcCommandContinue;
const MfClassicData* write_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
furi_string_reset(instance->text_box_store);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
} else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
furi_string_set(instance->text_box_store, "Use the source\ncard only");
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost);
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
const MfClassicData* tag_data = nfc_poller_get_data(instance->poller);
if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) {
mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite;
} else {
furi_string_set(
instance->text_box_store, "Use source card!\nTo write blanks\nuse NFC Magic app");
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
command = NfcCommandStop;
}
} else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) {
uint8_t sector = mfc_event->data->sec_tr_data.sector_num;
uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector);
if(mf_classic_is_block_read(write_data, sec_tr)) {
mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr];
mfc_event->data->sec_tr_data.sector_trailer_provided = true;
} else {
mfc_event->data->sec_tr_data.sector_trailer_provided = false;
}
} else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) {
uint8_t block_num = mfc_event->data->write_block_data.block_num;
if(mf_classic_is_block_read(write_data, block_num)) {
mfc_event->data->write_block_data.write_block = write_data->block[block_num];
mfc_event->data->write_block_data.write_block_provided = true;
} else {
mfc_event->data->write_block_data.write_block_provided = false;
}
} else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
furi_string_reset(instance->text_box_store);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
command = NfcCommandStop;
} else if(mfc_event->type == MfClassicPollerEventTypeFail) {
furi_string_set(instance->text_box_store, "Not all sectors\nwere written\ncorrectly");
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_write_on_enter_mf_classic(NfcApp* instance) {
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_classic, instance);
furi_string_set(instance->text_box_store, "Use the source\ncard only");
}
const NfcProtocolSupportBase nfc_protocol_support_mf_classic = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo |
NfcProtocolFeatureWrite,
.scene_info =
{
@@ -310,6 +365,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_classic = {
.on_enter = nfc_scene_emulate_on_enter_mf_classic,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_scene_write_on_enter_mf_classic,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(mf_classic, NfcProtocolMfClassic);
@@ -124,6 +124,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_desfire = {
.on_enter = nfc_scene_emulate_on_enter_mf_desfire,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(mf_desfire, NfcProtocolMfDesfire);
@@ -25,6 +25,20 @@ static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) {
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_mf_plus(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus);
furi_string_reset(instance->text_box_store);
nfc_render_mf_plus_data(data, instance->text_box_store);
text_box_set_font(instance->text_box, TextBoxFontHex);
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
}
static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolMfPlus);
@@ -78,7 +92,7 @@ static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) {
}
const NfcProtocolSupportBase nfc_protocol_support_mf_plus = {
.features = NfcProtocolFeatureEmulateUid,
.features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo,
.scene_info =
{
@@ -87,7 +101,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_plus = {
},
.scene_more_info =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_enter = nfc_scene_more_info_on_enter_mf_plus,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
@@ -120,6 +134,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_plus = {
.on_enter = nfc_scene_emulate_on_enter_mf_plus,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(mf_plus, NfcProtocolMfPlus);
@@ -15,7 +15,21 @@ void nfc_render_mf_plus_info(
}
void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str) {
nfc_render_mf_plus_version(&data->version, str);
MfPlusVersion empty_version = {0};
if(memcmp(&data->version, &empty_version, sizeof(MfPlusVersion)) == 0) {
const char* device_name = mf_plus_get_device_name(data, NfcDeviceNameTypeFull);
if(data->type == MfPlusTypeUnknown || data->size == MfPlusSizeUnknown ||
data->security_level == MfPlusSecurityLevelUnknown) {
furi_string_cat_printf(str, "This %s", device_name);
furi_string_replace(str, " Unknown", "");
} else {
furi_string_cat(str, device_name);
}
furi_string_replace(str, "Mifare", "MIFARE");
furi_string_cat(str, " does not support the GetVersion command, extra info unavailable\n");
} else {
nfc_render_mf_plus_version(&data->version, str);
}
}
void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str) {
@@ -14,7 +14,6 @@ enum {
SubmenuIndexUnlock = SubmenuIndexCommonMax,
SubmenuIndexUnlockByReader,
SubmenuIndexUnlockByPassword,
SubmenuIndexWrite,
};
enum {
@@ -182,24 +181,22 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
const MfUltralightData* data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
bool is_locked = !mf_ultralight_is_all_data_read(data);
if(!mf_ultralight_is_all_data_read(data)) {
if(is_locked ||
(data->type != MfUltralightTypeNTAG213 && data->type != MfUltralightTypeNTAG215 &&
data->type != MfUltralightTypeNTAG216 && data->type != MfUltralightTypeUL11 &&
data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin)) {
submenu_remove_item(submenu, SubmenuIndexCommonWrite);
}
if(is_locked) {
submenu_add_item(
submenu,
"Unlock",
SubmenuIndexUnlock,
nfc_protocol_support_common_submenu_callback,
instance);
} else if(
data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 ||
data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 ||
data->type == MfUltralightTypeUL21 || data->type == MfUltralightTypeOrigin) {
submenu_add_item(
submenu,
"Write",
SubmenuIndexWrite,
nfc_protocol_support_common_submenu_callback,
instance);
}
}
@@ -252,19 +249,57 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
NfcSceneMfUltralightUnlockMenu;
scene_manager_next_scene(instance->scene_manager, next_scene);
consumed = true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
consumed = true;
} else if(event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
consumed = true;
}
}
return consumed;
}
static NfcCommand
nfc_scene_write_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfUltralight);
NfcApp* instance = context;
MfUltralightPollerEvent* mf_ultralight_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) {
mf_ultralight_event->data->poller_mode = MfUltralightPollerModeWrite;
furi_string_reset(instance->text_box_store);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) {
mf_ultralight_event->data->auth_context.skip_auth = true;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestWriteData) {
mf_ultralight_event->data->write_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardMismatch) {
furi_string_set(instance->text_box_store, "Card of the same\ntype should be\n presented");
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardLocked) {
furi_string_set(
instance->text_box_store, "Card protected by\npassword, AUTH0\nor lock bits");
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteFail) {
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteSuccess) {
furi_string_reset(instance->text_box_store);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_write_on_enter_mf_ultralight(NfcApp* instance) {
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_ultralight, instance);
furi_string_set(instance->text_box_store, "Apply the initial\ncard only");
}
const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo |
NfcProtocolFeatureWrite,
.scene_info =
{
@@ -306,6 +341,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
.on_enter = nfc_scene_emulate_on_enter_mf_ultralight,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_scene_write_on_enter_mf_ultralight,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(mf_ultralight, NfcProtocolMfUltralight);
@@ -114,6 +114,8 @@ const char* nfc_protocol_support_plugin_names[NfcProtocolNum] = {
[NfcProtocolMfDesfire] = "mf_desfire",
[NfcProtocolSlix] = "slix",
[NfcProtocolSt25tb] = "st25tb",
[NfcProtocolNtag4xx] = "ntag4xx",
[NfcProtocolType4Tag] = "type_4_tag",
[NfcProtocolEmv] = "emv",
/* Add new protocol support plugin names here */
};
@@ -410,6 +412,15 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) {
instance);
}
if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureWrite)) {
submenu_add_item(
submenu,
"Write",
SubmenuIndexCommonWrite,
nfc_protocol_support_common_submenu_callback,
instance);
}
nfc_protocol_support_get(protocol, instance)->scene_read_menu.on_enter(instance);
submenu_add_item(
@@ -443,6 +454,13 @@ static bool
dolphin_deed(DolphinDeedNfcEmulate);
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
consumed = true;
} else if(event.event == SubmenuIndexCommonWrite) {
dolphin_deed(DolphinDeedNfcEmulate);
scene_manager_next_scene(instance->scene_manager, NfcSceneWrite);
consumed = true;
} else if(event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
consumed = true;
} else {
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
consumed = nfc_protocol_support_get(protocol, instance)
@@ -540,6 +558,15 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) {
instance);
}
if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureWrite)) {
submenu_add_item(
submenu,
"Write",
SubmenuIndexCommonWrite,
nfc_protocol_support_common_submenu_callback,
instance);
}
if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEditUid)) {
submenu_add_item(
submenu,
@@ -613,6 +640,12 @@ static bool
dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate);
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
consumed = true;
} else if(event.event == SubmenuIndexCommonWrite) {
const bool is_added =
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType);
dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate);
scene_manager_next_scene(instance->scene_manager, NfcSceneWrite);
consumed = true;
} else if(event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
consumed = true;
@@ -860,6 +893,191 @@ static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) {
nfc_blink_stop(instance);
}
// SceneWrite
/**
* @brief Current view displayed on the write scene.
*
* The emulation scene has five states, some protocols may not use all states.
* Protocol handles poller events, when scene state needs to change it should
* fill text_box_store with a short caption (when applicable) before sending
* the relevant view dispatcher event.
*/
enum {
NfcSceneWriteStateSearching, /**< Ask user to touch the card. Event: on_enter, CardLost. Needs caption. */
NfcSceneWriteStateWriting, /**< Ask not to move while writing. Event: CardDetected. No caption. */
NfcSceneWriteStateSuccess, /**< Card written successfully. Event: PollerSuccess. No caption. */
NfcSceneWriteStateFailure, /**< An error is displayed. Event: PollerFailure. Needs caption. */
NfcSceneWriteStateWrongCard, /**< Wrong card was presented. Event: WrongCard. Needs caption. */
};
static void nfc_protocol_support_scene_write_popup_callback(void* context) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit);
}
void nfc_protocol_support_scene_write_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort && result == GuiButtonTypeLeft) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventRetry);
}
}
static void nfc_protocol_support_scene_write_setup_view(NfcApp* instance) {
Popup* popup = instance->popup;
Widget* widget = instance->widget;
popup_reset(popup);
widget_reset(widget);
uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneWrite);
NfcView view = NfcViewPopup;
if(state == NfcSceneWriteStateSearching) {
popup_set_header(popup, "Writing", 95, 20, AlignCenter, AlignCenter);
popup_set_text(
popup,
furi_string_get_cstr(instance->text_box_store),
95,
38,
AlignCenter,
AlignCenter);
popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50);
} else if(state == NfcSceneWriteStateWriting) {
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
popup_set_icon(popup, 12, 23, &A_Loading_24);
} else if(state == NfcSceneWriteStateSuccess) {
popup_set_header(popup, "Successfully\nwritten!", 126, 2, AlignRight, AlignTop);
popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55);
popup_set_timeout(popup, 1500);
popup_set_context(popup, instance);
popup_set_callback(popup, nfc_protocol_support_scene_write_popup_callback);
popup_enable_timeout(popup);
} else if(state == NfcSceneWriteStateFailure) {
view = NfcViewWidget;
widget_add_string_element(
widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
widget_add_string_multiline_element(
widget,
7,
17,
AlignLeft,
AlignTop,
FontSecondary,
furi_string_get_cstr(instance->text_box_store));
widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
nfc_protocol_support_scene_write_widget_callback,
instance);
} else if(state == NfcSceneWriteStateWrongCard) {
view = NfcViewWidget;
widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wrong card!");
widget_add_string_multiline_element(
widget,
4,
17,
AlignLeft,
AlignTop,
FontSecondary,
furi_string_get_cstr(instance->text_box_store));
widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
nfc_protocol_support_scene_write_widget_callback,
instance);
}
view_dispatcher_switch_to_view(instance->view_dispatcher, view);
}
static void nfc_protocol_support_scene_write_on_enter(NfcApp* instance) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneWrite, NfcSceneWriteStateSearching);
furi_string_reset(instance->text_box_store);
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
// instance->poller is allocated in the respective on_enter() handler
nfc_protocol_support_get(protocol, instance)->scene_write.on_enter(instance);
nfc_protocol_support_scene_write_setup_view(instance);
nfc_blink_emulate_start(instance);
}
static bool nfc_protocol_support_scene_write_on_event(NfcApp* instance, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
uint32_t new_state = -1;
bool stop_poller = false;
if(event.event == NfcCustomEventCardDetected) {
new_state = NfcSceneWriteStateWriting;
consumed = true;
} else if(event.event == NfcCustomEventCardLost) {
new_state = NfcSceneWriteStateSearching;
consumed = true;
} else if(event.event == NfcCustomEventPollerSuccess) {
dolphin_deed(DolphinDeedNfcSave);
notification_message(instance->notifications, &sequence_success);
new_state = NfcSceneWriteStateSuccess;
stop_poller = true;
consumed = true;
} else if(event.event == NfcCustomEventPollerFailure) {
notification_message(instance->notifications, &sequence_error);
new_state = NfcSceneWriteStateFailure;
stop_poller = true;
consumed = true;
} else if(event.event == NfcCustomEventWrongCard) {
notification_message(instance->notifications, &sequence_error);
new_state = NfcSceneWriteStateWrongCard;
stop_poller = true;
consumed = true;
} else if(event.event == NfcCustomEventViewExit) {
scene_manager_previous_scene(instance->scene_manager);
consumed = true;
} else if(event.event == NfcCustomEventRetry) {
nfc_protocol_support_scenes[NfcProtocolSupportSceneWrite].on_exit(instance);
nfc_protocol_support_scenes[NfcProtocolSupportSceneWrite].on_enter(instance);
consumed = true;
}
if(stop_poller) {
if(instance->poller) {
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
instance->poller = NULL;
}
nfc_blink_stop(instance);
}
if(new_state != (uint32_t)-1) {
scene_manager_set_scene_state(instance->scene_manager, NfcSceneWrite, new_state);
nfc_protocol_support_scene_write_setup_view(instance);
}
}
return consumed;
}
static void nfc_protocol_support_scene_write_on_exit(NfcApp* instance) {
if(instance->poller) {
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
}
// Clear view
popup_reset(instance->popup);
widget_reset(instance->widget);
furi_string_reset(instance->text_box_store);
nfc_blink_stop(instance);
}
static void nfc_protocol_support_scene_rpc_on_enter(NfcApp* instance) {
UNUSED(instance);
}
@@ -975,6 +1193,12 @@ static const NfcProtocolSupportCommonSceneBase
.on_event = nfc_protocol_support_scene_emulate_on_event,
.on_exit = nfc_protocol_support_scene_emulate_on_exit,
},
[NfcProtocolSupportSceneWrite] =
{
.on_enter = nfc_protocol_support_scene_write_on_enter,
.on_event = nfc_protocol_support_scene_write_on_event,
.on_exit = nfc_protocol_support_scene_write_on_exit,
},
[NfcProtocolSupportSceneRpc] =
{
.on_enter = nfc_protocol_support_scene_rpc_on_enter,
@@ -116,6 +116,15 @@ typedef struct {
* It is responsible for creating a listener and for handling its events.
*/
NfcProtocolSupportSceneBase scene_emulate;
/**
* @brief Handlers for protocol-specific write scene.
*
* This scene is activated when a write operation is in progress.
* It is responsible for creating a poller, handling its events and
* displaying short captions for what is happening.
*/
NfcProtocolSupportSceneBase scene_write;
} NfcProtocolSupportBase;
/**
@@ -13,6 +13,7 @@ typedef enum {
NfcProtocolFeatureEmulateFull = 1UL << 1, /**< Complete emulation is supported. */
NfcProtocolFeatureEditUid = 1UL << 2, /**< UID editing is supported. */
NfcProtocolFeatureMoreInfo = 1UL << 3, /**< More information is provided. */
NfcProtocolFeatureWrite = 1UL << 4, /**< Writing to real card is supported. */
} NfcProtocolFeature;
/**
@@ -30,6 +31,7 @@ typedef enum {
NfcProtocolSupportSceneSavedMenu, /**< Menu for the card that was loaded from file. */
NfcProtocolSupportSceneSaveName, /**< Shown when saving or renaming a file. */
NfcProtocolSupportSceneEmulate, /**< Shown when emulating a card. */
NfcProtocolSupportSceneWrite, /**< Shown when writing to a card. */
NfcProtocolSupportSceneRpc, /**< Shown in remote-controlled (RPC) mode. */
NfcProtocolSupportSceneCount, /**< Special value equal to total scene count. Internal use. */
@@ -26,9 +26,9 @@ void nfc_protocol_support_common_byte_input_done_callback(void* context) {
}
void nfc_protocol_support_common_text_input_done_callback(void* context) {
NfcApp* nfc = context;
NfcApp* instance = context;
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventTextInputDone);
}
void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) {
@@ -15,6 +15,7 @@
enum {
SubmenuIndexCommonSave, /**< Save menu option. */
SubmenuIndexCommonEmulate, /**< Emulate menu option. */
SubmenuIndexCommonWrite, /**< Write menu option. */
SubmenuIndexCommonEdit, /**< Edit menu option. */
SubmenuIndexCommonInfo, /**< Info menu option. */
SubmenuIndexCommonRename, /**< Rename menu option. */
@@ -0,0 +1,140 @@
#include "ntag4xx.h"
#include "ntag4xx_render.h"
#include <nfc/protocols/ntag4xx/ntag4xx_poller.h>
#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"
static void nfc_scene_info_on_enter_ntag4xx(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx);
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_ntag4xx_info(data, NfcProtocolFormatTypeFull, temp_str);
widget_add_text_scroll_element(
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
furi_string_free(temp_str);
}
static void nfc_scene_more_info_on_enter_ntag4xx(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx);
furi_string_reset(instance->text_box_store);
nfc_render_ntag4xx_data(data, instance->text_box_store);
text_box_set_font(instance->text_box, TextBoxFontHex);
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
}
static NfcCommand nfc_scene_read_poller_callback_ntag4xx(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolNtag4xx);
NfcCommand command = NfcCommandContinue;
NfcApp* instance = context;
const Ntag4xxPollerEvent* ntag4xx_event = event.event_data;
if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolNtag4xx, nfc_poller_get_data(instance->poller));
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
command = NfcCommandStop;
} else if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadFailed) {
command = NfcCommandReset;
}
return command;
}
static void nfc_scene_read_on_enter_ntag4xx(NfcApp* instance) {
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_ntag4xx, instance);
}
static void nfc_scene_read_success_on_enter_ntag4xx(NfcApp* instance) {
const NfcDevice* device = instance->nfc_device;
const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx);
FuriString* temp_str = furi_string_alloc();
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_ntag4xx_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_ntag4xx(NfcApp* instance) {
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_ntag4xx = {
.features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo,
.scene_info =
{
.on_enter = nfc_scene_info_on_enter_ntag4xx,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_more_info =
{
.on_enter = nfc_scene_more_info_on_enter_ntag4xx,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read =
{
.on_enter = nfc_scene_read_on_enter_ntag4xx,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_read_success =
{
.on_enter = nfc_scene_read_success_on_enter_ntag4xx,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.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_ntag4xx,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(ntag4xx, NfcProtocolNtag4xx);
@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_ntag4xx;
@@ -0,0 +1,110 @@
#include "ntag4xx_render.h"
#include "../iso14443_4a/iso14443_4a_render.h"
void nfc_render_ntag4xx_info(
const Ntag4xxData* data,
NfcProtocolFormatType format_type,
FuriString* str) {
nfc_render_iso14443_4a_brief(ntag4xx_get_base_data(data), str);
const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version);
if(type >= Ntag4xxTypeUnknown) {
furi_string_cat(str, "Memory Size: unknown");
} else {
size_t size_cc = 32;
size_t size_ndef = 0;
size_t size_proprietary = 0;
bool has_tagtamper = false;
switch(type) {
case Ntag4xxType413DNA:
size_ndef = 128;
size_proprietary = 0;
break;
case Ntag4xxType424DNATT:
has_tagtamper = true;
/* fall-through */
case Ntag4xxType424DNA:
size_ndef = 256;
size_proprietary = 128;
break;
case Ntag4xxType426QDNATT:
has_tagtamper = true;
/* fall-through */
case Ntag4xxType426QDNA:
size_ndef = 768;
size_proprietary = 128;
break;
default:
break;
}
furi_string_cat_printf(
str, "\nMemory Size: %zu bytes\n", size_cc + size_ndef + size_proprietary);
furi_string_cat_printf(str, "Usable NDEF Size: %zu bytes\n", size_ndef - sizeof(uint16_t));
furi_string_cat_printf(str, "Capability Cont.: %zu bytes\n", size_cc);
if(size_proprietary) {
furi_string_cat_printf(str, "Proprietary File: %zu bytes\n", size_proprietary);
}
furi_string_cat_printf(str, "TagTamper: %ssupported", has_tagtamper ? "" : "not ");
}
if(format_type != NfcProtocolFormatTypeFull) return;
furi_string_cat(str, "\n\e#ISO14443-4 data");
nfc_render_iso14443_4a_extra(ntag4xx_get_base_data(data), str);
}
void nfc_render_ntag4xx_data(const Ntag4xxData* data, FuriString* str) {
nfc_render_ntag4xx_version(&data->version, str);
}
void nfc_render_ntag4xx_version(const Ntag4xxVersion* data, FuriString* str) {
furi_string_cat_printf(
str,
"%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
data->uid[0],
data->uid[1],
data->uid[2],
data->uid[3],
data->uid[4],
data->uid[5],
data->uid[6]);
furi_string_cat_printf(
str,
"hw %02x type %02x sub %02x\n"
" maj %02x min %02x\n"
" size %02x proto %02x\n",
data->hw_vendor,
data->hw_type,
data->hw_subtype,
data->hw_major,
data->hw_minor,
data->hw_storage,
data->hw_proto);
furi_string_cat_printf(
str,
"sw %02x type %02x sub %02x\n"
" maj %02x min %02x\n"
" size %02x proto %02x\n",
data->sw_vendor,
data->sw_type,
data->sw_subtype,
data->sw_major,
data->sw_minor,
data->sw_storage,
data->sw_proto);
furi_string_cat_printf(
str,
"batch %02x:%02x:%02x:%02x:%01x\n"
"week %d year %d\n"
"fab key %02x id %02x\n",
data->batch[0],
data->batch[1],
data->batch[2],
data->batch[3],
data->batch_extra,
data->prod_week,
data->prod_year,
(data->fab_key_4b << 1) | (data->fab_key_1b),
data->optional.fab_key_id);
}
@@ -0,0 +1,14 @@
#pragma once
#include <nfc/protocols/ntag4xx/ntag4xx.h>
#include "../nfc_protocol_support_render_common.h"
void nfc_render_ntag4xx_info(
const Ntag4xxData* data,
NfcProtocolFormatType format_type,
FuriString* str);
void nfc_render_ntag4xx_data(const Ntag4xxData* data, FuriString* str);
void nfc_render_ntag4xx_version(const Ntag4xxVersion* data, FuriString* str);
@@ -78,20 +78,21 @@ static NfcCommand nfc_scene_emulate_listener_callback_slix(NfcGenericEvent event
furi_assert(event.protocol == NfcProtocolSlix);
furi_assert(event.event_data);
NfcApp* nfc = context;
NfcApp* instance = context;
SlixListenerEvent* slix_event = event.event_data;
if(slix_event->type == SlixListenerEventTypeCustomCommand) {
if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(nfc->text_box_store, "R:");
if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(instance->text_box_store, "R:");
for(size_t i = 0; i < bit_buffer_get_size_bytes(slix_event->data->buffer); i++) {
furi_string_cat_printf(
nfc->text_box_store,
instance->text_box_store,
" %02X",
bit_buffer_get_byte(slix_event->data->buffer, i));
}
furi_string_push_back(nfc->text_box_store, '\n');
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
furi_string_push_back(instance->text_box_store, '\n');
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventListenerUpdate);
}
}
@@ -105,15 +106,6 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
}
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
return false;
}
const NfcProtocolSupportBase nfc_protocol_support_slix = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
@@ -145,7 +137,7 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = {
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_saved_menu_on_event_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
@@ -157,6 +149,11 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = {
.on_enter = nfc_scene_emulate_on_enter_slix,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(slix, NfcProtocolSlix);
@@ -61,15 +61,6 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) {
furi_string_free(temp_str);
}
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
return true;
}
return false;
}
const NfcProtocolSupportBase nfc_protocol_support_st25tb = {
.features = NfcProtocolFeatureNone,
@@ -96,7 +87,7 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = {
.scene_saved_menu =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_scene_saved_menu_on_event_st25tb,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_save_name =
{
@@ -108,6 +99,11 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = {
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.scene_write =
{
.on_enter = nfc_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(st25tb, NfcProtocolSt25tb);
@@ -0,0 +1,260 @@
#include "type_4_tag.h"
#include "type_4_tag_render.h"
#include <nfc/protocols/type_4_tag/type_4_tag_poller.h>
#include <nfc/protocols/type_4_tag/type_4_tag_listener.h>
#include <toolbox/pretty_format.h>
#include "nfc/nfc_app_i.h"
#include "../nfc_protocol_support_common.h"
#include "../nfc_protocol_support_gui_common.h"
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) {
if(simple_array_get_count(data->ndef_data) == 0) {
furi_string_cat_str(instance->text_box_store, "No NDEF data to show");
} else {
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_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 NfcCommand
nfc_scene_emulate_listener_callback_type_4_tag(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolType4Tag);
NfcApp* instance = context;
Type4TagListenerEvent* type_4_tag_event = event.event_data;
if(type_4_tag_event->type == Type4TagListenerEventTypeCustomCommand) {
if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) {
furi_string_cat_printf(instance->text_box_store, "R:");
for(size_t i = 0; i < bit_buffer_get_size_bytes(type_4_tag_event->data->buffer); i++) {
furi_string_cat_printf(
instance->text_box_store,
" %02X",
bit_buffer_get_byte(type_4_tag_event->data->buffer, i));
}
furi_string_push_back(instance->text_box_store, '\n');
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventListenerUpdate);
}
}
return NfcCommandContinue;
}
static void nfc_scene_emulate_on_enter_type_4_tag(NfcApp* instance) {
const Type4TagData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag);
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolType4Tag, data);
nfc_listener_start(
instance->listener, nfc_scene_emulate_listener_callback_type_4_tag, instance);
}
static NfcCommand
nfc_scene_write_poller_callback_type_4_tag(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolType4Tag);
NfcApp* instance = context;
Type4TagPollerEvent* type_4_tag_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(type_4_tag_event->type == Type4TagPollerEventTypeRequestMode) {
type_4_tag_event->data->poller_mode.mode = Type4TagPollerModeWrite;
type_4_tag_event->data->poller_mode.data =
nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag);
furi_string_reset(instance->text_box_store);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
} else if(type_4_tag_event->type == Type4TagPollerEventTypeWriteFail) {
const char* error_str = type_4_tag_event->data->error == Type4TagErrorCardLocked ?
"Card does not\nallow writing\nnew data" :
"Failed to\nwrite new data";
furi_string_set(instance->text_box_store, error_str);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
} else if(type_4_tag_event->type == Type4TagPollerEventTypeWriteSuccess) {
furi_string_reset(instance->text_box_store);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_write_on_enter_type_4_tag(NfcApp* instance) {
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolType4Tag);
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_type_4_tag, instance);
furi_string_set(instance->text_box_store, "Apply card\nto the back");
}
const NfcProtocolSupportBase nfc_protocol_support_type_4_tag = {
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo |
NfcProtocolFeatureWrite,
.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_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.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_protocol_support_common_on_enter_empty,
.on_event = nfc_protocol_support_common_on_event_empty,
},
.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,
},
.scene_write =
{
.on_enter = nfc_scene_write_on_enter_type_4_tag,
.on_event = nfc_protocol_support_common_on_event_empty,
},
};
NFC_PROTOCOL_SUPPORT_PLUGIN(type_4_tag, NfcProtocolType4Tag);
@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_type_4_tag;
@@ -0,0 +1,59 @@
#include "type_4_tag_render.h"
#include "../iso14443_4a/iso14443_4a_render.h"
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:::::::::::::::[Stored NDEF]:::::::::::::::\n");
furi_string_cat_printf(str, "Current NDEF Size: %lu", simple_array_get_count(data->ndef_data));
if(data->is_tag_specific) {
furi_string_cat(str, "\n::::::::::::::::::[Tag Specs]::::::::::::::::::\n");
furi_string_cat_printf(
str,
"Card: %s\n",
furi_string_empty(data->platform_name) ? "unknown" :
furi_string_get_cstr(data->platform_name));
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: %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: %02X%s\n",
data->ndef_read_lock,
data->ndef_read_lock == 0 ? " (unlocked)" : "");
furi_string_cat_printf(
str,
"Write Lock: %02X%s",
data->ndef_write_lock,
data->ndef_write_lock == 0 ? " (unlocked)" : "");
}
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) {
size_t ndef_len = simple_array_get_count(data->ndef_data);
if(ndef_len == 0) {
furi_string_cat_str(str, "No NDEF data to show");
return;
}
const uint8_t* ndef_data = simple_array_cget_data(data->ndef_data);
furi_string_cat_printf(str, "\e*");
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]);
}
}
}
@@ -0,0 +1,14 @@
#pragma once
#include <nfc/protocols/type_4_tag/type_4_tag.h>
#include "../nfc_protocol_support_render_common.h"
#define TYPE_4_TAG_RENDER_BYTES_PER_LINE (4U)
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);
@@ -20,6 +20,7 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <nfc/protocols/mf_classic/mf_classic.h>
#include <nfc/protocols/slix/slix.h>
#include <nfc/protocols/type_4_tag/type_4_tag.h>
#include <bit_lib.h>
#include <toolbox/pretty_format.h>
@@ -31,7 +32,8 @@
#define NDEF_PROTO_UL (1)
#define NDEF_PROTO_MFC (2)
#define NDEF_PROTO_SLIX (3)
#define NDEF_PROTO_TOTAL (4)
#define NDEF_PROTO_T4T (4)
#define NDEF_PROTO_TOTAL (5)
#ifndef NDEF_PROTO
#error Must specify what protocol to use with NDEF_PROTO define!
@@ -40,10 +42,10 @@
#error Invalid NDEF_PROTO specified!
#endif
#define NDEF_TITLE(device, parsed_data) \
furi_string_printf( \
parsed_data, \
"\e#NDEF Format Data\nCard type: %s\n", \
#define NDEF_TITLE(device, parsed_data) \
furi_string_printf( \
parsed_data, \
"\e#NDEF Format Data\nCard: %s\n", \
nfc_device_get_name(device, NfcDeviceNameTypeFull))
// ---=== structures ===---
@@ -151,6 +153,11 @@ typedef struct {
const uint8_t* start;
size_t size;
} slix;
#elif NDEF_PROTO == NDEF_PROTO_T4T
struct {
const uint8_t* data;
size_t size;
} t4t;
#endif
} Ndef;
@@ -230,6 +237,13 @@ static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) {
memcpy(buf, ndef->slix.start + pos, len);
return true;
#elif NDEF_PROTO == NDEF_PROTO_T4T
// Memory space is contiguous, simply need to remap to data pointer
if(pos + len > ndef->t4t.size) return false;
memcpy(buf, ndef->t4t.data + pos, len);
return true;
#else
UNUSED(ndef);
@@ -1039,6 +1053,44 @@ static bool ndef_slix_parse(const NfcDevice* device, FuriString* parsed_data) {
return parsed > 0;
}
#elif NDEF_PROTO == NDEF_PROTO_T4T
static bool ndef_t4t_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
furi_assert(parsed_data);
const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag);
size_t data_start = 0;
size_t data_size = simple_array_get_count(data->ndef_data);
NDEF_TITLE(device, parsed_data);
furi_string_replace(parsed_data, "Card: ", "Protocol: ");
if(data->is_tag_specific && !furi_string_empty(data->platform_name)) {
furi_string_cat_printf(
parsed_data, "Card: %s\n", furi_string_get_cstr(data->platform_name));
}
Ndef ndef = {
.output = parsed_data,
.t4t =
{
.data = data_size == 0 ? NULL : simple_array_cget_data(data->ndef_data),
.size = data_size,
},
};
size_t parsed = ndef_parse_message(&ndef, data_start, data_size - data_start, 1, false);
if(parsed) {
furi_string_trim(parsed_data, "\n");
furi_string_cat(parsed_data, "\n");
} else {
furi_string_reset(parsed_data);
}
return parsed > 0;
}
#endif
// ---=== boilerplate ===---
@@ -1056,6 +1108,9 @@ static const NfcSupportedCardsPlugin ndef_plugin = {
#elif NDEF_PROTO == NDEF_PROTO_SLIX
.parse = ndef_slix_parse,
.protocol = NfcProtocolSlix,
#elif NDEF_PROTO == NDEF_PROTO_T4T
.parse = ndef_t4t_parse,
.protocol = NfcProtocolType4Tag,
#endif
};
@@ -18,6 +18,7 @@ ADD_SCENE(nfc, extra_actions, ExtraActions)
ADD_SCENE(nfc, read_success, ReadSuccess)
ADD_SCENE(nfc, read_menu, ReadMenu)
ADD_SCENE(nfc, emulate, Emulate)
ADD_SCENE(nfc, write, Write)
ADD_SCENE(nfc, rpc, Rpc)
ADD_SCENE(nfc, debug, Debug)
ADD_SCENE(nfc, field, Field)
@@ -25,10 +26,6 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm)
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
ADD_SCENE(nfc, save_confirm, SaveConfirm)
ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite)
ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess)
ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail)
ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard)
ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
@@ -48,10 +45,6 @@ ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete)
ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial)
ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess)
ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard)
ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial)
ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess)
ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail)
ADD_SCENE(nfc, mf_classic_write_initial_wrong_card, MfClassicWriteInitialWrongCard)
ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList)
@@ -47,6 +47,9 @@ bool nfc_scene_mf_classic_update_initial_wrong_card_on_event(
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(instance->scene_manager);
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneSavedMenu);
}
return consumed;
}
@@ -1,148 +0,0 @@
#include "../nfc_app_i.h"
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
enum {
NfcSceneMfClassicWriteInitialStateCardSearch,
NfcSceneMfClassicWriteInitialStateCardFound,
};
NfcCommand
nfc_scene_mf_classic_write_initial_worker_callback(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.event_data);
furi_assert(event.protocol == NfcProtocolMfClassic);
NfcCommand command = NfcCommandContinue;
NfcApp* instance = context;
MfClassicPollerEvent* mfc_event = event.event_data;
const MfClassicData* write_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
} else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost);
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
const MfClassicData* tag_data = nfc_poller_get_data(instance->poller);
if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) {
mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite;
} else {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
command = NfcCommandStop;
}
} else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) {
uint8_t sector = mfc_event->data->sec_tr_data.sector_num;
uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector);
if(mf_classic_is_block_read(write_data, sec_tr)) {
mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr];
mfc_event->data->sec_tr_data.sector_trailer_provided = true;
} else {
mfc_event->data->sec_tr_data.sector_trailer_provided = false;
}
} else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) {
uint8_t block_num = mfc_event->data->write_block_data.block_num;
if(mf_classic_is_block_read(write_data, block_num)) {
mfc_event->data->write_block_data.write_block = write_data->block[block_num];
mfc_event->data->write_block_data.write_block_provided = true;
} else {
mfc_event->data->write_block_data.write_block_provided = false;
}
} else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
command = NfcCommandStop;
} else if(mfc_event->type == MfClassicPollerEventTypeFail) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) {
Popup* popup = instance->popup;
popup_reset(popup);
uint32_t state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial);
if(state == NfcSceneMfClassicWriteInitialStateCardSearch) {
popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter);
popup_set_text(
instance->popup, "Use the source\ncard only", 95, 38, AlignCenter, AlignCenter);
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
} else {
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
popup_set_icon(popup, 12, 23, &A_Loading_24);
}
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
}
void nfc_scene_mf_classic_write_initial_on_enter(void* context) {
NfcApp* instance = context;
dolphin_deed(DolphinDeedNfcEmulate);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfClassicWriteInitial,
NfcSceneMfClassicWriteInitialStateCardSearch);
nfc_scene_mf_classic_write_initial_setup_view(instance);
// Setup and start worker
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic);
nfc_poller_start(
instance->poller, nfc_scene_mf_classic_write_initial_worker_callback, instance);
nfc_blink_emulate_start(instance);
}
bool nfc_scene_mf_classic_write_initial_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventCardDetected) {
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfClassicWriteInitial,
NfcSceneMfClassicWriteInitialStateCardFound);
nfc_scene_mf_classic_write_initial_setup_view(instance);
consumed = true;
} else if(event.event == NfcCustomEventCardLost) {
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfClassicWriteInitial,
NfcSceneMfClassicWriteInitialStateCardSearch);
nfc_scene_mf_classic_write_initial_setup_view(instance);
consumed = true;
} else if(event.event == NfcCustomEventWrongCard) {
scene_manager_next_scene(
instance->scene_manager, NfcSceneMfClassicWriteInitialWrongCard);
consumed = true;
} else if(event.event == NfcCustomEventPollerSuccess) {
scene_manager_next_scene(
instance->scene_manager, NfcSceneMfClassicWriteInitialSuccess);
consumed = true;
} else if(event.event == NfcCustomEventPollerFailure) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitialFail);
consumed = true;
}
}
return consumed;
}
void nfc_scene_mf_classic_write_initial_on_exit(void* context) {
NfcApp* instance = context;
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfClassicWriteInitial,
NfcSceneMfClassicWriteInitialStateCardSearch);
// Clear view
popup_reset(instance->popup);
nfc_blink_stop(instance);
}
@@ -1,62 +0,0 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_classic_write_initial_fail_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
}
}
void nfc_scene_mf_classic_write_initial_fail_on_enter(void* context) {
NfcApp* instance = context;
Widget* widget = instance->widget;
notification_message(instance->notifications, &sequence_error);
widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_string_element(
widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
widget_add_string_multiline_element(
widget,
7,
17,
AlignLeft,
AlignTop,
FontSecondary,
"Not all sectors\nwere written\ncorrectly.");
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Finish",
nfc_scene_mf_classic_write_initial_fail_widget_callback,
instance);
// Setup and start worker
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
bool nfc_scene_mf_classic_write_initial_fail_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneSavedMenu);
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneSavedMenu);
}
return consumed;
}
void nfc_scene_mf_classic_write_initial_fail_on_exit(void* context) {
NfcApp* instance = context;
widget_reset(instance->widget);
}
@@ -1,43 +0,0 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_classic_write_initial_success_popup_callback(void* context) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit);
}
void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) {
NfcApp* instance = context;
dolphin_deed(DolphinDeedNfcSave);
notification_message(instance->notifications, &sequence_success);
Popup* popup = instance->popup;
popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop);
popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55);
popup_set_timeout(popup, 1500);
popup_set_context(popup, instance);
popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
}
bool nfc_scene_mf_classic_write_initial_success_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventViewExit) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneSavedMenu);
}
}
return consumed;
}
void nfc_scene_mf_classic_write_initial_success_on_exit(void* context) {
NfcApp* instance = context;
// Clear view
popup_reset(instance->popup);
}
@@ -1,57 +0,0 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_classic_write_initial_wrong_card_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
}
}
void nfc_scene_mf_classic_write_initial_wrong_card_on_enter(void* context) {
NfcApp* instance = context;
Widget* widget = instance->widget;
notification_message(instance->notifications, &sequence_error);
widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_string_element(
widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Use The Source Card!");
widget_add_string_multiline_element(
widget,
4,
17,
AlignLeft,
AlignTop,
FontSecondary,
"Go to NFC Magic\napp if you want to\nwrite blanks");
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
nfc_scene_mf_classic_write_initial_wrong_card_widget_callback,
instance);
// Setup and start worker
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
bool nfc_scene_mf_classic_write_initial_wrong_card_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(instance->scene_manager);
}
}
return consumed;
}
void nfc_scene_mf_classic_write_initial_wrong_card_on_exit(void* context) {
NfcApp* instance = context;
widget_reset(instance->widget);
}
@@ -1,120 +0,0 @@
#include "../nfc_app_i.h"
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
enum {
NfcSceneMfUltralightWriteStateCardSearch,
NfcSceneMfUltralightWriteStateCardFound,
};
NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.event_data);
furi_assert(event.protocol == NfcProtocolMfUltralight);
NfcCommand command = NfcCommandContinue;
NfcApp* instance = context;
MfUltralightPollerEvent* mfu_event = event.event_data;
if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) {
mfu_event->data->poller_mode = MfUltralightPollerModeWrite;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
mfu_event->data->auth_context.skip_auth = true;
} else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) {
mfu_event->data->write_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
} else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) {
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
command = NfcCommandStop;
}
return command;
}
static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) {
Popup* popup = instance->popup;
popup_reset(popup);
uint32_t state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite);
if(state == NfcSceneMfUltralightWriteStateCardSearch) {
popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter);
popup_set_text(
instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter);
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
} else {
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
popup_set_icon(popup, 12, 23, &A_Loading_24);
}
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
}
void nfc_scene_mf_ultralight_write_on_enter(void* context) {
NfcApp* instance = context;
dolphin_deed(DolphinDeedNfcEmulate);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightWrite,
NfcSceneMfUltralightWriteStateCardSearch);
nfc_scene_mf_ultralight_write_setup_view(instance);
// Setup and start worker
FURI_LOG_D("WMFU", "Card searching...");
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance);
nfc_blink_emulate_start(instance);
}
bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventCardDetected) {
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightWrite,
NfcSceneMfUltralightWriteStateCardFound);
nfc_scene_mf_ultralight_write_setup_view(instance);
consumed = true;
} else if(event.event == NfcCustomEventWrongCard) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard);
consumed = true;
} else if(event.event == NfcCustomEventPollerSuccess) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess);
consumed = true;
} else if(event.event == NfcCustomEventPollerFailure) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail);
consumed = true;
}
}
return consumed;
}
void nfc_scene_mf_ultralight_write_on_exit(void* context) {
NfcApp* instance = context;
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightWrite,
NfcSceneMfUltralightWriteStateCardSearch);
// Clear view
popup_reset(instance->popup);
nfc_blink_stop(instance);
}
@@ -1,67 +0,0 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_write_fail_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
}
}
void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) {
NfcApp* instance = context;
Widget* widget = instance->widget;
notification_message(instance->notifications, &sequence_error);
widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_string_element(
widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
widget_add_string_multiline_element(
widget,
7,
17,
AlignLeft,
AlignTop,
FontSecondary,
"Card protected by\npassword, AUTH0\nor lock bits");
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Finish",
nfc_scene_mf_ultralight_write_fail_widget_callback,
instance);
// Setup and start worker
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) {
bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu);
uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu;
return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id);
}
bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance);
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance);
}
return consumed;
}
void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) {
NfcApp* instance = context;
widget_reset(instance->widget);
}
@@ -1,46 +0,0 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit);
}
void nfc_scene_mf_ultralight_write_success_on_enter(void* context) {
NfcApp* instance = context;
dolphin_deed(DolphinDeedNfcSave);
notification_message(instance->notifications, &sequence_success);
Popup* popup = instance->popup;
popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58);
popup_set_header(popup, "Successfully\nwritten", 5, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
popup_set_context(popup, instance);
popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
}
bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventViewExit) {
bool was_saved =
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu);
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess);
}
}
return consumed;
}
void nfc_scene_mf_ultralight_write_success_on_exit(void* context) {
NfcApp* instance = context;
// Clear view
popup_reset(instance->popup);
}
@@ -1,58 +0,0 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_wrong_card_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
}
}
void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) {
NfcApp* instance = context;
Widget* widget = instance->widget;
notification_message(instance->notifications, &sequence_error);
widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_string_element(
widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
widget_add_string_multiline_element(
widget,
4,
17,
AlignLeft,
AlignTop,
FontSecondary,
"Card of the same\ntype should be\n presented");
//"Data management\nis only possible\nwith card of same type");
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
nfc_scene_mf_ultralight_wrong_card_widget_callback,
instance);
// Setup and start worker
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(instance->scene_manager);
}
}
return consumed;
}
void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) {
NfcApp* instance = context;
widget_reset(instance->widget);
}
@@ -0,0 +1,13 @@
#include "../helpers/protocol_support/nfc_protocol_support.h"
void nfc_scene_write_on_enter(void* context) {
nfc_protocol_support_on_enter(NfcProtocolSupportSceneWrite, context);
}
bool nfc_scene_write_on_event(void* context, SceneManagerEvent event) {
return nfc_protocol_support_on_event(NfcProtocolSupportSceneWrite, context, event);
}
void nfc_scene_write_on_exit(void* context) {
nfc_protocol_support_on_exit(NfcProtocolSupportSceneWrite, context);
}
@@ -358,6 +358,25 @@ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* lab
true);
}
void submenu_remove_item(Submenu* submenu, uint32_t index) {
furi_check(submenu);
with_view_model(
submenu->view,
SubmenuModel * model,
{
SubmenuItemArray_it_t it;
for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
SubmenuItemArray_next(it)) {
if(index == SubmenuItemArray_cref(it)->index) {
SubmenuItemArray_remove(model->items, it);
break;
}
}
},
true);
}
void submenu_reset(Submenu* submenu) {
furi_check(submenu);
view_set_orientation(submenu->view, ViewOrientationHorizontal);
@@ -98,6 +98,14 @@ void submenu_add_item_ex(
*/
void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label);
/** Remove item from submenu
*
* @param submenu Submenu instance
* @param index menu item index, used for callback, may be
* the same with other items, first one is removed
*/
void submenu_remove_item(Submenu* submenu, uint32_t index);
/** Remove all items from submenu
*
* @param submenu Submenu instance
+15 -6
View File
@@ -19,39 +19,48 @@ env.Append(
File("protocols/iso14443_3b/iso14443_3b.h"),
File("protocols/iso14443_4a/iso14443_4a.h"),
File("protocols/iso14443_4b/iso14443_4b.h"),
File("protocols/iso15693_3/iso15693_3.h"),
File("protocols/felica/felica.h"),
File("protocols/mf_ultralight/mf_ultralight.h"),
File("protocols/mf_classic/mf_classic.h"),
File("protocols/mf_plus/mf_plus.h"),
File("protocols/mf_desfire/mf_desfire.h"),
File("protocols/emv/emv.h"),
File("protocols/slix/slix.h"),
File("protocols/st25tb/st25tb.h"),
File("protocols/felica/felica.h"),
File("protocols/ntag4xx/ntag4xx.h"),
File("protocols/type_4_tag/type_4_tag.h"),
File("protocols/emv/emv.h"),
# Pollers
File("protocols/iso14443_3a/iso14443_3a_poller.h"),
File("protocols/iso14443_3b/iso14443_3b_poller.h"),
File("protocols/iso14443_4a/iso14443_4a_poller.h"),
File("protocols/iso14443_4b/iso14443_4b_poller.h"),
File("protocols/iso15693_3/iso15693_3_poller.h"),
File("protocols/felica/felica_poller.h"),
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
File("protocols/mf_classic/mf_classic_poller.h"),
File("protocols/mf_plus/mf_plus_poller.h"),
File("protocols/mf_desfire/mf_desfire_poller.h"),
File("protocols/slix/slix_poller.h"),
File("protocols/emv/emv_poller.h"),
File("protocols/st25tb/st25tb_poller.h"),
File("protocols/felica/felica_poller.h"),
File("protocols/ntag4xx/ntag4xx_poller.h"),
File("protocols/type_4_tag/type_4_tag_poller.h"),
File("protocols/emv/emv_poller.h"),
# Listeners
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
File("protocols/iso15693_3/iso15693_3_listener.h"),
File("protocols/felica/felica_listener.h"),
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
File("protocols/mf_classic/mf_classic_listener.h"),
File("protocols/felica/felica_listener.h"),
File("protocols/slix/slix_listener.h"),
File("protocols/type_4_tag/type_4_tag_listener.h"),
# Sync API
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
File("protocols/felica/felica_poller_sync.h"),
File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"),
File("protocols/mf_classic/mf_classic_poller_sync.h"),
File("protocols/st25tb/st25tb_poller_sync.h"),
File("protocols/felica/felica_poller_sync.h"),
# Misc
File("helpers/nfc_util.h"),
File("helpers/iso14443_crc.h"),
+124 -7
View File
@@ -14,6 +14,8 @@
#define ISO14443_4_BLOCK_PCB_MASK (0x03)
#define ISO14443_4_BLOCK_PCB_I (0U)
#define ISO14443_4_BLOCK_PCB_I_MASK (1U << 1)
#define ISO14443_4_BLOCK_PCB_I_ZERO_MASK (7U << 5)
#define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2)
#define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3)
#define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4)
@@ -33,8 +35,14 @@
#define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET)
#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET)
#define ISO14443_4_BLOCK_CID_MASK (0x0F)
#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask))
#define ISO14443_4_BLOCK_PCB_IS_I_BLOCK(pcb) \
(ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_MASK) && \
(((pcb) & ISO14443_4_BLOCK_PCB_I_ZERO_MASK) == 0))
#define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \
ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK)
@@ -47,14 +55,23 @@
#define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \
ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK)
#define ISO14443_4_LAYER_NAD_NOT_SUPPORTED ((uint8_t) - 1)
#define ISO14443_4_LAYER_NAD_NOT_SET ((uint8_t) - 2)
struct Iso14443_4Layer {
uint8_t pcb;
uint8_t pcb_prev;
// Listener specific
uint8_t cid;
uint8_t nad;
};
static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) {
static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance, bool toggle_num) {
instance->pcb_prev = instance->pcb;
instance->pcb ^= (uint8_t)0x01;
if(toggle_num) {
instance->pcb ^= (uint8_t)0x01;
}
}
Iso14443_4Layer* iso14443_4_layer_alloc(void) {
@@ -73,6 +90,9 @@ void iso14443_4_layer_reset(Iso14443_4Layer* instance) {
furi_assert(instance);
instance->pcb_prev = 0;
instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB;
instance->cid = ISO14443_4_LAYER_CID_NOT_SUPPORTED;
instance->nad = ISO14443_4_LAYER_NAD_NOT_SUPPORTED;
}
void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) {
@@ -96,7 +116,7 @@ void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool
(CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB;
}
void iso14443_4_layer_encode_block(
void iso14443_4_layer_encode_command(
Iso14443_4Layer* instance,
const BitBuffer* input_data,
BitBuffer* block_data) {
@@ -105,7 +125,7 @@ void iso14443_4_layer_encode_block(
bit_buffer_append_byte(block_data, instance->pcb);
bit_buffer_append(block_data, input_data);
iso14443_4_layer_update_pcb(instance);
iso14443_4_layer_update_pcb(instance, true);
}
static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) {
@@ -113,7 +133,7 @@ static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_d
return data[0];
}
bool iso14443_4_layer_decode_block(
bool iso14443_4_layer_decode_response(
Iso14443_4Layer* instance,
BitBuffer* output_data,
const BitBuffer* block_data) {
@@ -127,7 +147,7 @@ bool iso14443_4_layer_decode_block(
ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) &&
(!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb));
instance->pcb &= ISO14443_4_BLOCK_PCB_MASK;
iso14443_4_layer_update_pcb(instance);
iso14443_4_layer_update_pcb(instance, true);
} else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) {
const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data);
ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) &&
@@ -147,7 +167,7 @@ bool iso14443_4_layer_decode_block(
return ret;
}
Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext(
Iso14443_4Layer* instance,
BitBuffer* output_data,
const BitBuffer* block_data) {
@@ -199,3 +219,100 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
return ret;
}
void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid) {
instance->cid = cid;
}
void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad) {
instance->nad = nad ? 0 : ISO14443_4_LAYER_NAD_NOT_SUPPORTED;
}
Iso14443_4LayerResult iso14443_4_layer_decode_command(
Iso14443_4Layer* instance,
const BitBuffer* input_data,
BitBuffer* block_data) {
furi_assert(instance);
uint8_t prologue_len = 0;
instance->pcb = bit_buffer_get_byte(input_data, prologue_len++);
if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb)) {
if(instance->pcb & ISO14443_4_BLOCK_PCB_I_CID_MASK) {
const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) &
ISO14443_4_BLOCK_CID_MASK;
if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) {
return Iso14443_4LayerResultSkip;
}
} else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) {
return Iso14443_4LayerResultSkip;
}
// TODO: properly handle block chaining
if(instance->pcb & ISO14443_4_BLOCK_PCB_I_NAD_MASK) {
if(instance->nad == ISO14443_4_LAYER_NAD_NOT_SUPPORTED) {
return Iso14443_4LayerResultSkip;
}
instance->nad = bit_buffer_get_byte(input_data, prologue_len++);
}
bit_buffer_copy_right(block_data, input_data, prologue_len);
iso14443_4_layer_update_pcb(instance, false);
return Iso14443_4LayerResultData;
} else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb)) {
if(instance->pcb & ISO14443_4_BLOCK_PCB_S_CID_MASK) {
const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) &
ISO14443_4_BLOCK_CID_MASK;
if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) {
return Iso14443_4LayerResultSkip;
}
} else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) {
return Iso14443_4LayerResultSkip;
}
if((instance->pcb & ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) == 0) {
// DESELECT
bit_buffer_copy(block_data, input_data);
return Iso14443_4LayerResultSend | Iso14443_4LayerResultHalt;
} else {
// WTX ACK or wrong value
return Iso14443_4LayerResultSkip;
}
} else if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb)) {
// TODO: properly handle R blocks while chaining
iso14443_4_layer_update_pcb(instance, true);
instance->pcb |= ISO14443_4_BLOCK_PCB_R_NACK_MASK;
bit_buffer_reset(block_data);
bit_buffer_append_byte(block_data, instance->pcb);
iso14443_4_layer_update_pcb(instance, false);
return Iso14443_4LayerResultSend;
}
return Iso14443_4LayerResultSkip;
}
bool iso14443_4_layer_encode_response(
Iso14443_4Layer* instance,
const BitBuffer* input_data,
BitBuffer* block_data) {
furi_assert(instance);
if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb_prev)) {
bit_buffer_append_byte(block_data, 0x00);
if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_CID_MASK) {
bit_buffer_append_byte(block_data, instance->cid);
}
// TODO: properly handle block chaining and related R block responses
if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_NAD_MASK &&
instance->nad != ISO14443_4_LAYER_NAD_NOT_SET) {
bit_buffer_append_byte(block_data, instance->nad);
instance->nad = ISO14443_4_LAYER_NAD_NOT_SET;
} else {
instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_NAD_MASK;
}
instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_CHAIN_MASK;
bit_buffer_set_byte(block_data, 0, instance->pcb);
bit_buffer_append(block_data, input_data);
iso14443_4_layer_update_pcb(instance, false);
return true;
}
return false;
}
+29 -3
View File
@@ -19,21 +19,47 @@ void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool
void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present);
void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present);
void iso14443_4_layer_encode_block(
// Poller mode
void iso14443_4_layer_encode_command(
Iso14443_4Layer* instance,
const BitBuffer* input_data,
BitBuffer* block_data);
bool iso14443_4_layer_decode_block(
bool iso14443_4_layer_decode_response(
Iso14443_4Layer* instance,
BitBuffer* output_data,
const BitBuffer* block_data);
Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext(
Iso14443_4Layer* instance,
BitBuffer* output_data,
const BitBuffer* block_data);
// Listener mode
typedef enum {
Iso14443_4LayerResultSkip = (0),
Iso14443_4LayerResultData = (1 << 1),
Iso14443_4LayerResultSend = (1 << 2),
Iso14443_4LayerResultHalt = (1 << 3),
} Iso14443_4LayerResult;
Iso14443_4LayerResult iso14443_4_layer_decode_command(
Iso14443_4Layer* instance,
const BitBuffer* input_data,
BitBuffer* block_data);
bool iso14443_4_layer_encode_response(
Iso14443_4Layer* instance,
const BitBuffer* input_data,
BitBuffer* block_data);
#define ISO14443_4_LAYER_CID_NOT_SUPPORTED ((uint8_t) - 1)
void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid);
void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad);
#ifdef __cplusplus
}
#endif
+115
View File
@@ -0,0 +1,115 @@
#include "nxp_native_command.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
#define TAG "NxpNativeCommand"
Iso14443_4aError nxp_native_command_iso14443_4a_poller(
Iso14443_4aPoller* iso14443_4a_poller,
NxpNativeCommandStatus* status_code,
const BitBuffer* input_buffer,
BitBuffer* result_buffer,
NxpNativeCommandMode command_mode,
BitBuffer* tx_buffer,
BitBuffer* rx_buffer) {
furi_check(iso14443_4a_poller);
furi_check(tx_buffer);
furi_check(rx_buffer);
furi_check(input_buffer);
furi_check(result_buffer);
furi_check(command_mode < NxpNativeCommandModeMAX);
Iso14443_4aError error = Iso14443_4aErrorNone;
*status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK;
do {
bit_buffer_reset(tx_buffer);
if(command_mode == NxpNativeCommandModePlain) {
bit_buffer_append(tx_buffer, input_buffer);
} else if(command_mode == NxpNativeCommandModeIsoWrapped) {
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA);
bit_buffer_append_byte(tx_buffer, bit_buffer_get_byte(input_buffer, 0));
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1);
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2);
if(bit_buffer_get_size_bytes(input_buffer) > 1) {
bit_buffer_append_byte(tx_buffer, bit_buffer_get_size_bytes(input_buffer) - 1);
bit_buffer_append_right(tx_buffer, input_buffer, 1);
}
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE);
}
bit_buffer_reset(rx_buffer);
error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer);
if(error != Iso14443_4aErrorNone) {
break;
}
bit_buffer_reset(tx_buffer);
if(command_mode == NxpNativeCommandModePlain) {
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME);
} else if(command_mode == NxpNativeCommandModeIsoWrapped) {
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA);
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME);
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1);
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2);
bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE);
}
size_t response_len = bit_buffer_get_size_bytes(rx_buffer);
*status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR;
bit_buffer_reset(result_buffer);
if(command_mode == NxpNativeCommandModePlain && response_len >= sizeof(uint8_t)) {
*status_code = bit_buffer_get_byte(rx_buffer, 0);
if(response_len > sizeof(uint8_t)) {
bit_buffer_copy_right(result_buffer, rx_buffer, sizeof(uint8_t));
}
} else if(
command_mode == NxpNativeCommandModeIsoWrapped &&
response_len >= 2 * sizeof(uint8_t) &&
bit_buffer_get_byte(rx_buffer, response_len - 2) == NXP_NATIVE_COMMAND_ISO_SW1) {
*status_code = bit_buffer_get_byte(rx_buffer, response_len - 1);
if(response_len > 2 * sizeof(uint8_t)) {
bit_buffer_copy_left(result_buffer, rx_buffer, response_len - 2 * sizeof(uint8_t));
}
}
while(*status_code == NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME) {
bit_buffer_reset(rx_buffer);
error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer);
if(error != Iso14443_4aErrorNone) {
break;
}
const size_t rx_size = bit_buffer_get_size_bytes(rx_buffer);
const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(result_buffer) -
bit_buffer_get_size_bytes(result_buffer);
if(command_mode == NxpNativeCommandModePlain) {
*status_code = rx_size >= 1 ? bit_buffer_get_byte(rx_buffer, 0) :
NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR;
if(rx_size <= rx_capacity_remaining + 1) {
bit_buffer_append_right(result_buffer, rx_buffer, sizeof(uint8_t));
} else {
FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1);
}
} else if(command_mode == NxpNativeCommandModeIsoWrapped) {
if(rx_size >= 2 &&
bit_buffer_get_byte(rx_buffer, rx_size - 2) == NXP_NATIVE_COMMAND_ISO_SW1) {
*status_code = bit_buffer_get_byte(rx_buffer, rx_size - 1);
} else {
*status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR;
}
if(rx_size <= rx_capacity_remaining + 2) {
bit_buffer_set_size_bytes(rx_buffer, rx_size - 2);
bit_buffer_append(result_buffer, rx_buffer);
} else {
FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 2);
}
}
}
} while(false);
return error;
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include "nxp_native_command_mode.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
// ISO 7816 command wrapping
#define NXP_NATIVE_COMMAND_ISO_CLA (0x90)
#define NXP_NATIVE_COMMAND_ISO_P1 (0x00)
#define NXP_NATIVE_COMMAND_ISO_P2 (0x00)
#define NXP_NATIVE_COMMAND_ISO_LE (0x00)
// ISO 7816 status wrapping
#define NXP_NATIVE_COMMAND_ISO_SW1 (0x91)
// Successful operation
#define NXP_NATIVE_COMMAND_STATUS_OPERATION_OK (0x00)
// No changes done to backup files, CommitTransaction / AbortTransaction not necessary
#define NXP_NATIVE_COMMAND_STATUS_NO_CHANGES (0x0C)
// Insufficient NV-Memory to complete command
#define NXP_NATIVE_COMMAND_STATUS_OUT_OF_EEPROM_ERROR (0x0E)
// Command code not supported
#define NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE (0x1C)
// CRC or MAC does not match data Padding bytes not valid
#define NXP_NATIVE_COMMAND_STATUS_INTEGRITY_ERROR (0x1E)
// Invalid key number specified
#define NXP_NATIVE_COMMAND_STATUS_NO_SUCH_KEY (0x40)
// Length of command string invalid
#define NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR (0x7E)
// Current configuration / status does not allow the requested command
#define NXP_NATIVE_COMMAND_STATUS_PERMISSION_DENIED (0x9D)
// Value of the parameter(s) invalid
#define NXP_NATIVE_COMMAND_STATUS_PARAMETER_ERROR (0x9E)
// Requested AID not present on PICC
#define NXP_NATIVE_COMMAND_STATUS_APPLICATION_NOT_FOUND (0xA0)
// Unrecoverable error within application, application will be disabled
#define NXP_NATIVE_COMMAND_STATUS_APPL_INTEGRITY_ERROR (0xA1)
// Currently not allowed to authenticate. Keep trying until full delay is spent
#define NXP_NATIVE_COMMAND_STATUS_STATUS_AUTHENTICATION_DELAY (0xAD)
// Current authentication status does not allow the requested command
#define NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR (0xAE)
// Additional data frame is expected to be sent
#define NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME (0xAF)
// Attempt to read/write data from/to beyond the file's/record's limits
// Attempt to exceed the limits of a value file.
#define NXP_NATIVE_COMMAND_STATUS_BOUNDARY_ERROR (0xBE)
// Unrecoverable error within PICC, PICC will be disabled
#define NXP_NATIVE_COMMAND_STATUS_PICC_INTEGRITY_ERROR (0xC1)
// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD
#define NXP_NATIVE_COMMAND_STATUS_COMMAND_ABORTED (0xCA)
// PICC was disabled by an unrecoverable error
#define NXP_NATIVE_COMMAND_STATUS_PICC_DISABLED_ERROR (0xCD)
// Number of Applications limited to 28, no additional CreateApplication possible
#define NXP_NATIVE_COMMAND_STATUS_COUNT_ERROR (0xCE)
// Creation of file/application failed because file/application with same number already exists
#define NXP_NATIVE_COMMAND_STATUS_DUBLICATE_ERROR (0xDE)
// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated
#define NXP_NATIVE_COMMAND_STATUS_EEPROM_ERROR (0xEE)
// Specified file number does not exist
#define NXP_NATIVE_COMMAND_STATUS_FILE_NOT_FOUND (0xF0)
// Unrecoverable error within file, file will be disabled
#define NXP_NATIVE_COMMAND_STATUS_FILE_INTEGRITY_ERROR (0xF1)
typedef uint8_t NxpNativeCommandStatus;
/**
* @brief Transmit and receive NXP Native Command chunks in poller mode.
*
* Must ONLY be used inside the callback function.
*
* The result_buffer will be filled with any data received as a response to data
* sent from input_buffer, with a timeout defined by the fwt parameter.
*
* The tx_buffer and rx_buffer are used as working areas to handle individual
* command chunks and responses.
*
* @param[in, out] iso14443_4a_poller pointer to the instance to be used in the transaction.
* @param[out] status_code pointer to a status variable to hold the result of the operation.
* @param[in] input_buffer pointer to the buffer containing the data to be transmitted.
* @param[out] result_buffer pointer to the buffer to be filled with received data.
* @param[in] command_mode what command formatting mode to use for the transaction.
* @param[in, out] tx_buffer pointer to the buffer for command construction.
* @param[in, out] rx_buffer pointer to the buffer for response handling.
* @return Iso14443_4aErrorNone and STATUS_OPERATION_OK on success, an error code on failure.
*/
Iso14443_4aError nxp_native_command_iso14443_4a_poller(
Iso14443_4aPoller* iso14443_4a_poller,
NxpNativeCommandStatus* status_code,
const BitBuffer* input_buffer,
BitBuffer* result_buffer,
NxpNativeCommandMode command_mode,
BitBuffer* tx_buffer,
BitBuffer* rx_buffer);
+11
View File
@@ -0,0 +1,11 @@
#pragma once
/**
* @brief Enumeration of possible command modes.
*/
typedef enum {
NxpNativeCommandModePlain, /**< Plain native commands. */
NxpNativeCommandModeIsoWrapped, /**< ISO 7816-wrapped commands. */
NxpNativeCommandModeMAX,
} NxpNativeCommandMode;
@@ -26,6 +26,47 @@ typedef struct {
Iso14443_3aListenerEventData* data;
} Iso14443_3aListenerEvent;
/**
* @brief Transmit Iso14443_3a frames in listener mode.
*
* Must ONLY be used inside the callback function.
*
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @return Iso14443_3aErrorNone on success, an error code on failure.
*/
Iso14443_3aError
iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer);
/**
* @brief Transmit Iso14443_3a frames with custom parity bits in listener mode.
*
* Must ONLY be used inside the callback function.
*
* Custom parity bits must be set in the tx_buffer.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @return Iso14443_3aErrorNone on success, an error code on failure.
*/
Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity(
Iso14443_3aListener* instance,
const BitBuffer* tx_buffer);
/**
* @brief Transmit Iso14443_3a standard frames in listener mode.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @return Iso14443_3aErrorNone on success, an error code on failure.
*/
Iso14443_3aError iso14443_3a_listener_send_standard_frame(
Iso14443_3aListener* instance,
const BitBuffer* tx_buffer);
#ifdef __cplusplus
}
#endif
@@ -26,17 +26,6 @@ struct Iso14443_3aListener {
void* context;
};
Iso14443_3aError
iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer);
Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity(
Iso14443_3aListener* instance,
const BitBuffer* tx_buffer);
Iso14443_3aError iso14443_3a_listener_send_standard_frame(
Iso14443_3aListener* instance,
const BitBuffer* tx_buffer);
#ifdef __cplusplus
}
#endif
@@ -2,7 +2,8 @@
#include "iso14443_4a.h"
#define ISO14443_4A_CMD_READ_ATS (0xE0)
#define ISO14443_4A_CMD_READ_ATS (0xE0)
#define ISO14443_4A_READ_ATS_CID_MASK (0x0F)
// ATS bit definitions
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
@@ -14,7 +14,9 @@ static Iso14443_4aListener*
Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener));
instance->iso14443_3a_listener = iso14443_3a_listener;
instance->data = data;
instance->iso14443_4_layer = iso14443_4_layer_alloc();
instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE);
instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE);
instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data;
@@ -27,13 +29,18 @@ static Iso14443_4aListener*
static void iso14443_4a_listener_free(Iso14443_4aListener* instance) {
furi_assert(instance);
furi_assert(instance->data);
furi_assert(instance->tx_buffer);
iso14443_4_layer_free(instance->iso14443_4_layer);
bit_buffer_free(instance->rx_buffer);
bit_buffer_free(instance->tx_buffer);
free(instance);
}
static void iso14443_4a_listener_reset(Iso14443_4aListener* instance) {
instance->state = Iso14443_4aListenerStateIdle;
iso14443_4_layer_reset(instance->iso14443_4_layer);
}
static void iso14443_4a_listener_set_callback(
Iso14443_4aListener* instance,
NfcGenericCallback callback,
@@ -68,20 +75,46 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context)
if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) ==
Iso14443_4aErrorNone) {
instance->state = Iso14443_4aListenerStateActive;
if(iso14443_4a_supports_frame_option(
instance->data, Iso14443_4aFrameOptionCid)) {
const uint8_t cid = bit_buffer_get_byte(rx_buffer, 1) &
ISO14443_4A_READ_ATS_CID_MASK;
iso14443_4_layer_set_cid(instance->iso14443_4_layer, cid);
}
iso14443_4_layer_set_nad_supported(
instance->iso14443_4_layer,
iso14443_4a_supports_frame_option(
instance->data, Iso14443_4aFrameOptionNad));
}
}
} else {
instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData;
instance->iso14443_4a_event.data->buffer = rx_buffer;
Iso14443_4LayerResult status = iso14443_4_layer_decode_command(
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer);
if(status & Iso14443_4LayerResultSend) {
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->rx_buffer);
}
if(status & Iso14443_4LayerResultHalt) {
iso14443_4a_listener_reset(instance);
if(instance->callback) {
instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeHalted;
instance->callback(instance->generic_event, instance->context);
}
command = NfcCommandSleep;
}
if(status & Iso14443_4LayerResultData) {
instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData;
instance->iso14443_4a_event.data->buffer = instance->rx_buffer;
if(instance->callback) {
command = instance->callback(instance->generic_event, instance->context);
if(instance->callback) {
command = instance->callback(instance->generic_event, instance->context);
}
}
}
} else if(
iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) {
instance->state = Iso14443_4aListenerStateIdle;
iso14443_4a_listener_reset(instance);
instance->iso14443_4a_event.type = iso14443_3a_event->type ==
Iso14443_3aListenerEventTypeHalted ?
@@ -25,6 +25,18 @@ typedef struct {
Iso14443_4aListenerEventData* data;
} Iso14443_4aListenerEvent;
/**
* @brief Transmit Iso14443_4a blocks in listener mode.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @return Iso14443_4aErrorNone on success, an error code on failure.
*/
Iso14443_4aError
iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer);
#ifdef __cplusplus
}
#endif
@@ -30,3 +30,17 @@ Iso14443_4aError
instance->iso14443_3a_listener, instance->tx_buffer);
return iso14443_4a_process_error(error);
}
Iso14443_4aError
iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer) {
bit_buffer_reset(instance->tx_buffer);
if(!iso14443_4_layer_encode_response(
instance->iso14443_4_layer, tx_buffer, instance->tx_buffer)) {
return Iso14443_4aErrorProtocol;
}
const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
return iso14443_4a_process_error(error);
}
@@ -1,6 +1,7 @@
#pragma once
#include <nfc/protocols/nfc_generic_event.h>
#include <nfc/helpers/iso14443_4_layer.h>
#include "iso14443_4a_listener.h"
#include "iso14443_4a_i.h"
@@ -17,8 +18,10 @@ typedef enum {
struct Iso14443_4aListener {
Iso14443_3aListener* iso14443_3a_listener;
Iso14443_4aData* data;
Iso14443_4Layer* iso14443_4_layer;
Iso14443_4aListenerState state;
BitBuffer* rx_buffer;
BitBuffer* tx_buffer;
NfcGenericEvent generic_event;
@@ -65,7 +65,7 @@ Iso14443_4aError iso14443_4a_poller_send_block(
furi_check(rx_buffer);
bit_buffer_reset(instance->tx_buffer);
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
Iso14443_4aError error = Iso14443_4aErrorNone;
@@ -106,7 +106,7 @@ Iso14443_4aError iso14443_4a_poller_send_block(
} while(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX));
}
if(!iso14443_4_layer_decode_block(
if(!iso14443_4_layer_decode_response(
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
error = Iso14443_4aErrorProtocol;
break;
@@ -155,7 +155,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS;
bit_buffer_reset(instance->tx_buffer);
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
Iso14443_4aError error = Iso14443_4aErrorNone;
@@ -180,7 +180,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
break;
} else {
error = iso14443_4_layer_decode_block_pwt_ext(
error = iso14443_4_layer_decode_response_pwt_ext(
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer);
if(error == Iso14443_4aErrorSendExtra) {
if(--attempts_left == 0) break;
@@ -24,7 +24,7 @@ Iso14443_4bError iso14443_4b_poller_send_block(
furi_check(rx_buffer);
bit_buffer_reset(instance->tx_buffer);
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
Iso14443_4bError error = Iso14443_4bErrorNone;
@@ -36,7 +36,7 @@ Iso14443_4bError iso14443_4b_poller_send_block(
error = iso14443_4b_process_error(iso14443_3b_error);
break;
} else if(!iso14443_4_layer_decode_block(
} else if(!iso14443_4_layer_decode_response(
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
error = Iso14443_4bErrorProtocol;
break;
@@ -17,6 +17,13 @@ extern "C" {
#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F)
#define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5)
#define MF_DESFIRE_CMD_CREATE_APPLICATION (0xCA)
#define MF_DESFIRE_CMD_CREATE_STD_DATA_FILE (0xCD)
#define MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE (0xCB)
#define MF_DESFIRE_CMD_CREATE_VALUE_FILE (0xCC)
#define MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE (0xC1)
#define MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE (0xC0)
#define MF_DESFIRE_CMD_READ_DATA (0xBD)
#define MF_DESFIRE_CMD_GET_VALUE (0x6C)
#define MF_DESFIRE_CMD_READ_RECORDS (0xBB)
+26 -11
View File
@@ -60,7 +60,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) {
bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion));
}
return can_parse;
return can_parse && (data->hw_type & 0x0F) == 0x01;
}
bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) {
@@ -81,17 +81,17 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu
return can_parse;
}
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) {
typedef struct FURI_PACKED {
bool is_master_key_changeable : 1;
bool is_free_directory_list : 1;
bool is_free_create_delete : 1;
bool is_config_changeable : 1;
uint8_t change_key_id : 4;
uint8_t max_keys : 4;
uint8_t flags : 4;
} MfDesfireKeySettingsLayout;
typedef struct FURI_PACKED {
bool is_master_key_changeable : 1;
bool is_free_directory_list : 1;
bool is_free_create_delete : 1;
bool is_config_changeable : 1;
uint8_t change_key_id : 4;
uint8_t max_keys : 4;
uint8_t flags : 4;
} MfDesfireKeySettingsLayout;
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) {
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout);
if(can_parse) {
@@ -111,6 +111,21 @@ bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer*
return can_parse;
}
void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf) {
MfDesfireKeySettingsLayout layout;
layout.is_master_key_changeable = data->is_master_key_changeable;
layout.is_free_directory_list = data->is_free_directory_list;
layout.is_free_create_delete = data->is_free_create_delete;
layout.is_config_changeable = data->is_config_changeable;
layout.change_key_id = data->change_key_id;
layout.max_keys = data->max_keys;
layout.flags = data->flags;
bit_buffer_append_bytes(buf, (uint8_t*)&layout, sizeof(MfDesfireKeySettingsLayout));
}
bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) {
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion);
+4 -46
View File
@@ -2,55 +2,11 @@
#include "mf_desfire.h"
#include <nfc/helpers/nxp_native_command.h>
#define MF_DESFIRE_FFF_PICC_PREFIX "PICC"
#define MF_DESFIRE_FFF_APP_PREFIX "Application"
// Successful operation
#define MF_DESFIRE_STATUS_OPERATION_OK (0x00)
// No changes done to backup files, CommitTransaction / AbortTransaction not necessary
#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C)
// Insufficient NV-Memory to complete command
#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E)
// Command code not supported
#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C)
// CRC or MAC does not match data Padding bytes not valid
#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E)
// Invalid key number specified
#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40)
// Length of command string invalid
#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E)
// Current configuration / status does not allow the requested command
#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D)
// Value of the parameter(s) invalid
#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E)
// Requested AID not present on PICC
#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0)
// Unrecoverable error within application, application will be disabled
#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1)
// Current authentication status does not allow the requested command
#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE)
// Additional data frame is expected to be sent
#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF)
// Attempt to read/write data from/to beyond the file's/record's limits
// Attempt to exceed the limits of a value file.
#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE)
// Unrecoverable error within PICC, PICC will be disabled
#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1)
// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD
#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA)
// PICC was disabled by an unrecoverable error
#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD)
// Number of Applications limited to 28, no additional CreateApplication possible
#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE)
// Creation of file/application failed because file/application with same number already exists
#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE)
// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated
#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE)
// Specified file number does not exist
#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0)
// Unrecoverable error within file, file will be disabled
#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1)
// SimpleArray configurations
extern const SimpleArrayConfig mf_desfire_key_version_array_config;
@@ -68,6 +24,8 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf);
void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf);
bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf);
bool mf_desfire_application_id_parse(
@@ -251,8 +251,7 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) {
MfDesfireError error = mf_desfire_poller_read_key_version(instance, 0, &key_version);
if(error != MfDesfireErrorNone) break;
MfDesfireVersion version = {};
error = mf_desfire_poller_read_version(instance, &version);
error = mf_desfire_poller_read_version(instance, &instance->data->version);
if(error != MfDesfireErrorNone) break;
protocol_detected = true;
@@ -3,6 +3,7 @@
#include "mf_desfire.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
#include <lib/nfc/helpers/nxp_native_command_mode.h>
#ifdef __cplusplus
extern "C" {
@@ -38,6 +39,16 @@ typedef struct {
MfDesfirePollerEventData* data; /**< Pointer to event specific data. */
} MfDesfirePollerEvent;
/**
* @brief Change command mode used in poller mode.
*
* @param[in, out] instance pointer to the instance to affect.
* @param[in] command_mode command mode to use in further communication with the card.
*/
void mf_desfire_poller_set_command_mode(
MfDesfirePoller* instance,
NxpNativeCommandMode command_mode);
/**
* @brief Transmit and receive MfDesfire chunks in poller mode.
*
@@ -51,11 +62,16 @@ typedef struct {
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
* @return MfDesfireErrorNone on success, an error code on failure.
*/
MfDesfireError mf_desfire_send_chunks(
MfDesfireError mf_desfire_poller_send_chunks(
MfDesfirePoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer);
/**
* @warning deprecated, use mf_desfire_poller_send_chunks instead
*/
#define mf_desfire_send_chunks mf_desfire_poller_send_chunks
/**
* @brief Read MfDesfire card version.
*
@@ -187,6 +203,44 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi(
const SimpleArray* file_ids,
SimpleArray* data);
/**
* @brief Create Application on MfDesfire 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] id pointer to the application id for the new application.
* @param[in] key_settings pointer to the key settings for the new application.
* @param[in] iso_df_id optional iso identifier for the new application.
* @param[in] iso_df_name optional iso name for the new application.
* @param[in] iso_df_name_len length of the optional iso application name.
* @return MfDesfireErrorNone on success, an error code on failure.
*/
MfDesfireError mf_desfire_poller_create_application(
MfDesfirePoller* instance,
const MfDesfireApplicationId* id,
const MfDesfireKeySettings* key_settings,
uint16_t iso_df_id,
const uint8_t* iso_df_name,
uint8_t iso_df_name_len);
/**
* @brief Create File on MfDesfire 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] id file id for the new file.
* @param[in] data pointer to the file settings for the new file.
* @param[in] iso_ef_id optional iso identifier for the new file.
* @return MfDesfireErrorNone on success, an error code on failure.
*/
MfDesfireError mf_desfire_poller_create_file(
MfDesfirePoller* instance,
MfDesfireFileId id,
const MfDesfireFileSettings* data,
uint16_t iso_ef_id);
/**
* @brief Read file data on MfDesfire card.
*
@@ -1,6 +1,7 @@
#include "mf_desfire_poller_i.h"
#include <furi.h>
#include <bit_lib/bit_lib.h>
#include "mf_desfire_i.h"
@@ -21,76 +22,48 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) {
MfDesfireError mf_desfire_process_status_code(uint8_t status_code) {
switch(status_code) {
case MF_DESFIRE_STATUS_OPERATION_OK:
case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK:
return MfDesfireErrorNone;
case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR:
case NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR:
return MfDesfireErrorAuthentication;
case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE:
case NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE:
return MfDesfireErrorCommandNotSupported;
default:
return MfDesfireErrorProtocol;
}
}
MfDesfireError mf_desfire_send_chunks(
void mf_desfire_poller_set_command_mode(
MfDesfirePoller* instance,
NxpNativeCommandMode command_mode) {
furi_check(instance);
furi_check(instance->state == MfDesfirePollerStateIdle);
furi_check(command_mode < NxpNativeCommandModeMAX);
instance->command_mode = command_mode;
}
MfDesfireError mf_desfire_poller_send_chunks(
MfDesfirePoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer) {
furi_check(instance);
furi_check(instance->iso14443_4a_poller);
furi_check(instance->tx_buffer);
furi_check(instance->rx_buffer);
furi_check(tx_buffer);
furi_check(rx_buffer);
MfDesfireError error = MfDesfireErrorNone;
NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK;
Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller(
instance->iso14443_4a_poller,
&status_code,
tx_buffer,
rx_buffer,
instance->command_mode,
instance->tx_buffer,
instance->rx_buffer);
do {
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer);
if(iso14443_4a_error != Iso14443_4aErrorNone) {
error = mf_desfire_process_error(iso14443_4a_error);
break;
}
bit_buffer_reset(instance->tx_buffer);
bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME);
if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) {
bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
} else {
bit_buffer_reset(rx_buffer);
}
while(
bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) {
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
if(iso14443_4a_error != Iso14443_4aErrorNone) {
error = mf_desfire_process_error(iso14443_4a_error);
break;
}
const size_t rx_size = bit_buffer_get_size_bytes(instance->rx_buffer);
const size_t rx_capacity_remaining =
bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer);
if(rx_size <= rx_capacity_remaining + 1) {
bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
} else {
FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1);
}
}
} while(false);
if(error == MfDesfireErrorNone) {
uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0);
error = mf_desfire_process_status_code(err_code);
if(iso14443_4a_error != Iso14443_4aErrorNone) {
return mf_desfire_process_error(iso14443_4a_error);
}
return error;
return mf_desfire_process_status_code(status_code);
}
MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) {
@@ -102,7 +75,8 @@ MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfi
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -124,7 +98,8 @@ MfDesfireError
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -146,7 +121,8 @@ MfDesfireError
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -170,7 +146,7 @@ MfDesfireError mf_desfire_poller_read_key_version(
bit_buffer_set_byte(instance->input_buffer, 1, key_num);
MfDesfireError error =
mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
if(error == MfDesfireErrorNone) {
if(!mf_desfire_key_version_parse(data, instance->result_buffer)) {
error = MfDesfireErrorProtocol;
@@ -210,7 +186,8 @@ MfDesfireError
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -243,7 +220,7 @@ MfDesfireError mf_desfire_poller_select_application(
instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId));
MfDesfireError error =
mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
return error;
}
@@ -258,7 +235,8 @@ MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, Simple
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -293,7 +271,8 @@ MfDesfireError mf_desfire_poller_read_file_settings(
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -329,6 +308,108 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi(
return error;
}
MfDesfireError mf_desfire_poller_create_application(
MfDesfirePoller* instance,
const MfDesfireApplicationId* id,
const MfDesfireKeySettings* key_settings,
uint16_t iso_df_id,
const uint8_t* iso_df_name,
uint8_t iso_df_name_len) {
furi_check(instance);
furi_check(key_settings);
bit_buffer_reset(instance->input_buffer);
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_CREATE_APPLICATION);
bit_buffer_append_bytes(
instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId));
mf_desfire_key_settings_dump(key_settings, instance->input_buffer);
if(iso_df_name && iso_df_name_len) {
uint8_t ks2_pos = bit_buffer_get_size_bytes(instance->input_buffer) - 1;
uint8_t ks2 = bit_buffer_get_byte(instance->input_buffer, ks2_pos);
ks2 |= (1 << 5); // Mark file id present
bit_buffer_set_byte(instance->input_buffer, ks2_pos, ks2);
uint8_t iso_df_id_le[sizeof(iso_df_id)];
bit_lib_num_to_bytes_le(iso_df_id, sizeof(iso_df_id_le), iso_df_id_le);
bit_buffer_append_bytes(instance->input_buffer, iso_df_id_le, sizeof(iso_df_id_le));
bit_buffer_append_bytes(instance->input_buffer, iso_df_name, iso_df_name_len);
}
MfDesfireError error =
mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
return error;
}
MfDesfireError mf_desfire_poller_create_file(
MfDesfirePoller* instance,
MfDesfireFileId id,
const MfDesfireFileSettings* data,
uint16_t iso_ef_id) {
furi_check(instance);
furi_check(data);
bit_buffer_reset(instance->input_buffer);
bit_buffer_append_byte(
instance->input_buffer,
data->type == MfDesfireFileTypeStandard ? MF_DESFIRE_CMD_CREATE_STD_DATA_FILE :
data->type == MfDesfireFileTypeBackup ? MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE :
data->type == MfDesfireFileTypeValue ? MF_DESFIRE_CMD_CREATE_VALUE_FILE :
data->type == MfDesfireFileTypeLinearRecord ? MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE :
data->type == MfDesfireFileTypeCyclicRecord ? MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE :
0x00);
bit_buffer_append_byte(instance->input_buffer, id);
if(iso_ef_id) {
uint8_t iso_ef_id_le[sizeof(iso_ef_id)];
bit_lib_num_to_bytes_le(iso_ef_id, sizeof(iso_ef_id_le), iso_ef_id_le);
bit_buffer_append_bytes(instance->input_buffer, iso_ef_id_le, sizeof(iso_ef_id_le));
}
bit_buffer_append_byte(instance->input_buffer, data->comm);
bit_buffer_append_bytes(
instance->input_buffer,
(const uint8_t*)data->access_rights,
sizeof(MfDesfireFileAccessRights) * data->access_rights_len);
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
uint8_t data_size_le[3 * sizeof(uint8_t)];
bit_lib_num_to_bytes_le(data->data.size, sizeof(data_size_le), data_size_le);
bit_buffer_append_bytes(instance->input_buffer, data_size_le, sizeof(data_size_le));
} else if(data->type == MfDesfireFileTypeValue) {
uint8_t lo_limit_le[sizeof(data->value.lo_limit)];
bit_lib_num_to_bytes_le(data->value.lo_limit, sizeof(lo_limit_le), lo_limit_le);
bit_buffer_append_bytes(instance->input_buffer, lo_limit_le, sizeof(lo_limit_le));
uint8_t hi_limit_le[sizeof(data->value.hi_limit)];
bit_lib_num_to_bytes_le(data->value.hi_limit, sizeof(hi_limit_le), hi_limit_le);
bit_buffer_append_bytes(instance->input_buffer, hi_limit_le, sizeof(hi_limit_le));
uint8_t value_le[sizeof(data->value.limited_credit_value)];
bit_lib_num_to_bytes_le(data->value.limited_credit_value, sizeof(value_le), value_le);
bit_buffer_append_bytes(instance->input_buffer, value_le, sizeof(value_le));
bit_buffer_append_byte(instance->input_buffer, data->value.limited_credit_enabled);
} else if(
data->type == MfDesfireFileTypeLinearRecord ||
data->type == MfDesfireFileTypeCyclicRecord) {
uint8_t record_size_le[3 * sizeof(uint8_t)];
bit_lib_num_to_bytes_le(data->record.size, sizeof(record_size_le), record_size_le);
bit_buffer_append_bytes(instance->input_buffer, record_size_le, sizeof(record_size_le));
uint8_t record_max_le[3 * sizeof(uint8_t)];
bit_lib_num_to_bytes_le(data->record.max, sizeof(record_max_le), record_max_le);
bit_buffer_append_bytes(instance->input_buffer, record_max_le, sizeof(record_max_le));
}
MfDesfireError error =
mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
return error;
}
static MfDesfireError mf_desfire_poller_read_file(
MfDesfirePoller* instance,
MfDesfireFileId id,
@@ -354,7 +435,8 @@ static MfDesfireError mf_desfire_poller_read_file(
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&current_offset, 3);
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3);
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer);
@@ -400,7 +482,8 @@ MfDesfireError mf_desfire_poller_read_file_value(
MfDesfireError error;
do {
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
error = mf_desfire_poller_send_chunks(
instance, instance->input_buffer, instance->result_buffer);
if(error != MfDesfireErrorNone) break;
@@ -30,6 +30,7 @@ typedef enum {
struct MfDesfirePoller {
Iso14443_4aPoller* iso14443_4a_poller;
NxpNativeCommandMode command_mode;
MfDesfirePollerSessionState session_state;
MfDesfirePollerState state;
MfDesfireError error;
+30 -52
View File
@@ -15,8 +15,8 @@
const uint8_t mf_plus_ats_t1_tk_values[][MF_PLUS_T1_TK_VALUE_LEN] = {
{0xC1, 0x05, 0x2F, 0x2F, 0x00, 0x35, 0xC7}, // Mifare Plus S
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X
{0xC1, 0x05, 0x2F, 0x2F, 0x00, 0xF6, 0xD1}, // Mifare Plus SE
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE
{0xC1, 0x05, 0x21, 0x30, 0x00, 0xF6, 0xD1}, // Mifare Plus SE
{0xC1, 0x05, 0x21, 0x30, 0x10, 0xF6, 0xD1}, // Mifare Plus SE
};
MfPlusError mf_plus_get_type_from_version(
@@ -27,7 +27,7 @@ MfPlusError mf_plus_get_type_from_version(
MfPlusError error = MfPlusErrorProtocol;
if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) {
if((mf_plus_data->version.hw_type & 0x0F) == 0x02) {
error = MfPlusErrorNone;
// Mifare Plus EV1/EV2
@@ -85,16 +85,15 @@ MfPlusError
MfPlusError error = MfPlusErrorProtocol;
if(simple_array_get_count(iso4_data->ats_data.t1_tk) != MF_PLUS_T1_TK_VALUE_LEN) {
const size_t historical_bytes_len = simple_array_get_count(iso4_data->ats_data.t1_tk);
if(historical_bytes_len != MF_PLUS_T1_TK_VALUE_LEN) {
return MfPlusErrorProtocol;
}
const uint8_t* historical_bytes = simple_array_cget_data(iso4_data->ats_data.t1_tk);
switch(iso4_data->iso14443_3a_data->sak) {
case 0x08:
if(memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[0],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) {
// Mifare Plus S 2K SL1
mf_plus_data->type = MfPlusTypeS;
mf_plus_data->size = MfPlusSize2K;
@@ -102,11 +101,7 @@ MfPlusError
FURI_LOG_D(TAG, "Mifare Plus S 2K SL1");
error = MfPlusErrorNone;
} else if(
memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[1],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
} else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) {
// Mifare Plus X 2K SL1
mf_plus_data->type = MfPlusTypeX;
mf_plus_data->size = MfPlusSize2K;
@@ -115,14 +110,8 @@ MfPlusError
FURI_LOG_D(TAG, "Mifare Plus X 2K SL1");
error = MfPlusErrorNone;
} else if(
memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[2],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 ||
memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[3],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 ||
memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) {
// Mifare Plus SE 1K SL1
mf_plus_data->type = MfPlusTypeSE;
mf_plus_data->size = MfPlusSize1K;
@@ -154,10 +143,7 @@ MfPlusError
break;
case 0x18:
if(memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[0],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) {
// Mifare Plus S 4K SL1
mf_plus_data->type = MfPlusTypeS;
mf_plus_data->size = MfPlusSize4K;
@@ -165,11 +151,7 @@ MfPlusError
FURI_LOG_D(TAG, "Mifare Plus S 4K SL1");
error = MfPlusErrorNone;
} else if(
memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[1],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
} else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) {
// Mifare Plus X 4K SL1
mf_plus_data->type = MfPlusTypeX;
mf_plus_data->size = MfPlusSize4K;
@@ -183,10 +165,7 @@ MfPlusError
break;
case 0x20:
if(memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[0],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) {
// Mifare Plus S 2/4K SL3
FURI_LOG_D(TAG, "Mifare Plus S SL3");
mf_plus_data->type = MfPlusTypeS;
@@ -207,21 +186,20 @@ MfPlusError
} else {
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)");
}
} else if(
memcmp(
simple_array_get_data(iso4_data->ats_data.t1_tk),
mf_plus_ats_t1_tk_values[1],
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
} else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) {
// Mifare Plus X 2/4K SL3
mf_plus_data->type = MfPlusTypeX;
mf_plus_data->security_level = MfPlusSecurityLevel3;
FURI_LOG_D(TAG, "Mifare Plus X SL3");
if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) {
// Mifare Plus X 2K SL3
mf_plus_data->size = MfPlusSize2K;
FURI_LOG_D(TAG, "Mifare Plus X 2K SL3");
error = MfPlusErrorNone;
} else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) {
// Mifare Plus X 4K SL3
mf_plus_data->size = MfPlusSize4K;
FURI_LOG_D(TAG, "Mifare Plus X 4K SL3");
@@ -229,6 +207,16 @@ MfPlusError
} else {
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)");
}
} else if(
memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 ||
memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) {
// Mifare Plus SE 1K SL3
mf_plus_data->type = MfPlusTypeSE;
mf_plus_data->size = MfPlusSize1K;
mf_plus_data->security_level = MfPlusSecurityLevel3;
FURI_LOG_D(TAG, "Mifare Plus SE 1K SL3");
error = MfPlusErrorNone;
} else {
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type");
}
@@ -238,22 +226,12 @@ MfPlusError
}
MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) {
bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
if(can_parse) {
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
} else if(
bit_buffer_get_size_bytes(buf) == 8 &&
bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) {
// HACK(-nofl): There are supposed to be three parts to the GetVersion command,
// with the second and third parts fetched by sending the AdditionalFrame
// command. I don't know whether the entire MIFARE Plus line uses status as
// the first byte, so let's just assume we only have the first part of
// the response if it's size 8 and starts with the AF status. The second
// part of the response is the same size and status byte, but so far
// we're only reading one response.
can_parse = true;
bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1);
} else {
memset(data, 0, sizeof(MfPlusVersion));
}
return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol;
+2 -3
View File
@@ -2,10 +2,9 @@
#include "mf_plus.h"
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
#include <nfc/helpers/nxp_native_command.h>
#define MF_PLUS_STATUS_OPERATION_OK (0x90)
#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF)
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
MfPlusError mf_plus_get_type_from_version(
const Iso14443_4aData* iso14443_4a_data,
+23 -15
View File
@@ -19,28 +19,36 @@ MfPlusError mf_plus_process_error(Iso14443_4aError error) {
}
}
MfPlusError mf_plus_poller_send_chunk(
MfPlusError mf_plus_process_status_code(uint8_t status_code) {
switch(status_code) {
case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK:
return MfPlusErrorNone;
default:
return MfPlusErrorProtocol;
}
}
MfPlusError mf_plus_poller_send_chunks(
MfPlusPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer) {
furi_assert(instance);
furi_assert(instance->iso14443_4a_poller);
furi_assert(instance->tx_buffer);
furi_assert(instance->rx_buffer);
furi_assert(tx_buffer);
furi_assert(rx_buffer);
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer);
MfPlusError error = mf_plus_process_error(iso14443_4a_error);
NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK;
Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller(
instance->iso14443_4a_poller,
&status_code,
tx_buffer,
rx_buffer,
NxpNativeCommandModePlain,
instance->tx_buffer,
instance->rx_buffer);
if(error == MfPlusErrorNone) {
bit_buffer_copy(rx_buffer, instance->rx_buffer);
if(iso14443_4a_error != Iso14443_4aErrorNone) {
return mf_plus_process_error(iso14443_4a_error);
}
bit_buffer_reset(instance->tx_buffer);
return error;
return mf_plus_process_status_code(status_code);
}
MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) {
@@ -50,7 +58,7 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d
bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION);
MfPlusError error =
mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer);
mf_plus_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
if(error == MfPlusErrorNone) {
error = mf_plus_version_parse(data, instance->result_buffer);
}
+5 -1
View File
@@ -22,9 +22,11 @@
#include <nfc/protocols/mf_classic/mf_classic.h>
#include <nfc/protocols/mf_plus/mf_plus.h>
#include <nfc/protocols/mf_desfire/mf_desfire.h>
#include <nfc/protocols/emv/emv.h>
#include <nfc/protocols/slix/slix_device_defs.h>
#include <nfc/protocols/st25tb/st25tb.h>
#include <nfc/protocols/ntag4xx/ntag4xx.h>
#include <nfc/protocols/type_4_tag/type_4_tag.h>
#include <nfc/protocols/emv/emv.h>
/**
* @brief List of registered NFC device implementations.
@@ -45,6 +47,8 @@ const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = {
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
[NfcProtocolSlix] = &nfc_device_slix,
[NfcProtocolSt25tb] = &nfc_device_st25tb,
[NfcProtocolNtag4xx] = &nfc_device_ntag4xx,
[NfcProtocolType4Tag] = &nfc_device_type_4_tag,
[NfcProtocolEmv] = &nfc_device_emv,
/* Add new protocols here */
};
+6 -2
View File
@@ -3,10 +3,11 @@
#include <nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h>
#include <nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h>
#include <nfc/protocols/iso15693_3/iso15693_3_listener_defs.h>
#include <nfc/protocols/felica/felica_listener_defs.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h>
#include <nfc/protocols/mf_classic/mf_classic_listener_defs.h>
#include <nfc/protocols/slix/slix_listener_defs.h>
#include <nfc/protocols/felica/felica_listener_defs.h>
#include <nfc/protocols/type_4_tag/type_4_tag_listener_defs.h>
const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = {
[NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a,
@@ -14,11 +15,14 @@ const NfcListenerBase* const 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,
[NfcProtocolMfPlus] = NULL,
[NfcProtocolMfDesfire] = NULL,
[NfcProtocolSlix] = &nfc_listener_slix,
[NfcProtocolSt25tb] = NULL,
[NfcProtocolFelica] = &nfc_listener_felica,
[NfcProtocolNtag4xx] = NULL,
[NfcProtocolType4Tag] = &nfc_listener_type_4_tag,
[NfcProtocolEmv] = NULL,
};
+5 -1
View File
@@ -10,9 +10,11 @@
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
#include <nfc/protocols/mf_plus/mf_plus_poller_defs.h>
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
#include <nfc/protocols/emv/emv_poller_defs.h>
#include <nfc/protocols/slix/slix_poller_defs.h>
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
#include <nfc/protocols/ntag4xx/ntag4xx_poller_defs.h>
#include <nfc/protocols/type_4_tag/type_4_tag_poller_defs.h>
#include <nfc/protocols/emv/emv_poller_defs.h>
const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = {
[NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a,
@@ -27,6 +29,8 @@ const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = {
[NfcProtocolMfDesfire] = &mf_desfire_poller,
[NfcProtocolSlix] = &nfc_poller_slix,
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
[NfcProtocolNtag4xx] = &ntag4xx_poller,
[NfcProtocolType4Tag] = &type_4_tag_poller,
[NfcProtocolEmv] = &emv_poller,
/* Add new pollers here */
};
+28 -14
View File
@@ -12,19 +12,19 @@
* ```
* **************************** Protocol tree structure ***************************
*
* (Start)
* |
* +------------------------+-----------+---------+------------+
* | | | | |
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
* | | |
* +---------------+-------------+ ISO14443-4B SLIX
* | | |
* ISO14443-4A Mf Ultralight Mf Classic
* |
* +-----+-----+
* | |
* Mf Desfire EMV
* (Start)
* |
* +------------------------+-----------+---------+------------+
* | | | | |
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
* | | |
* +---------------+-------------+ ISO14443-4B SLIX
* | | |
* ISO14443-4A Mf Ultralight Mf Classic
* |
* +-----+----+----------+----------+---------+
* | | | | |
* Mf Desfire Mf Plus NTAG4xx Type 4 Tag EMV
* ```
*
* When implementing a new protocol, its place in the tree must be determined first.
@@ -62,8 +62,10 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = {
/** List of ISO14443-4A child protocols. */
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
NfcProtocolMfDesfire,
NfcProtocolMfPlus,
NfcProtocolMfDesfire,
NfcProtocolNtag4xx,
NfcProtocolType4Tag,
NfcProtocolEmv,
};
@@ -156,6 +158,18 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = {
.children_num = 0,
.children_protocol = NULL,
},
[NfcProtocolNtag4xx] =
{
.parent_protocol = NfcProtocolIso14443_4a,
.children_num = 0,
.children_protocol = NULL,
},
[NfcProtocolType4Tag] =
{
.parent_protocol = NfcProtocolIso14443_4a,
.children_num = 0,
.children_protocol = NULL,
},
[NfcProtocolEmv] =
{
.parent_protocol = NfcProtocolIso14443_4a,
+2
View File
@@ -188,6 +188,8 @@ typedef enum {
NfcProtocolMfDesfire,
NfcProtocolSlix,
NfcProtocolSt25tb,
NfcProtocolNtag4xx,
NfcProtocolType4Tag,
NfcProtocolEmv,
/* Add new protocols here */
+192
View File
@@ -0,0 +1,192 @@
#include "ntag4xx_i.h"
#include <furi.h>
#define NTAG4XX_PROTOCOL_NAME "NTAG4xx"
#define NTAG4XX_HW_MAJOR_TYPE_413_DNA (0x10)
#define NTAG4XX_HW_MAJOR_TYPE_424_DNA (0x30)
#define NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG (0x08)
static const char* ntag4xx_type_strings[] = {
[Ntag4xxType413DNA] = "NTAG413 DNA",
[Ntag4xxType424DNA] = "NTAG424 DNA",
[Ntag4xxType424DNATT] = "NTAG424 DNA TagTamper",
[Ntag4xxType426QDNA] = "NTAG426Q DNA",
[Ntag4xxType426QDNATT] = "NTAG426Q DNA TagTamper",
[Ntag4xxTypeUnknown] = "UNK",
};
const NfcDeviceBase nfc_device_ntag4xx = {
.protocol_name = NTAG4XX_PROTOCOL_NAME,
.alloc = (NfcDeviceAlloc)ntag4xx_alloc,
.free = (NfcDeviceFree)ntag4xx_free,
.reset = (NfcDeviceReset)ntag4xx_reset,
.copy = (NfcDeviceCopy)ntag4xx_copy,
.verify = (NfcDeviceVerify)ntag4xx_verify,
.load = (NfcDeviceLoad)ntag4xx_load,
.save = (NfcDeviceSave)ntag4xx_save,
.is_equal = (NfcDeviceEqual)ntag4xx_is_equal,
.get_name = (NfcDeviceGetName)ntag4xx_get_device_name,
.get_uid = (NfcDeviceGetUid)ntag4xx_get_uid,
.set_uid = (NfcDeviceSetUid)ntag4xx_set_uid,
.get_base_data = (NfcDeviceGetBaseData)ntag4xx_get_base_data,
};
Ntag4xxData* ntag4xx_alloc(void) {
Ntag4xxData* data = malloc(sizeof(Ntag4xxData));
data->iso14443_4a_data = iso14443_4a_alloc();
data->device_name = furi_string_alloc();
return data;
}
void ntag4xx_free(Ntag4xxData* data) {
furi_check(data);
ntag4xx_reset(data);
iso14443_4a_free(data->iso14443_4a_data);
furi_string_free(data->device_name);
free(data);
}
void ntag4xx_reset(Ntag4xxData* data) {
furi_check(data);
iso14443_4a_reset(data->iso14443_4a_data);
memset(&data->version, 0, sizeof(Ntag4xxVersion));
}
void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other) {
furi_check(data);
furi_check(other);
ntag4xx_reset(data);
iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data);
data->version = other->version;
}
bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type) {
UNUSED(data);
UNUSED(device_type);
return false;
}
bool ntag4xx_load(Ntag4xxData* 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;
if(!ntag4xx_version_load(&data->version, ff)) break;
success = true;
} while(false);
furi_string_free(prefix);
return success;
}
bool ntag4xx_save(const Ntag4xxData* 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, NTAG4XX_PROTOCOL_NAME " specific data")) break;
if(!ntag4xx_version_save(&data->version, ff)) break;
success = true;
} while(false);
furi_string_free(prefix);
return success;
}
bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other) {
furi_check(data);
furi_check(other);
return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) &&
memcmp(&data->version, &other->version, sizeof(Ntag4xxVersion)) == 0;
}
Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version) {
Ntag4xxType type = Ntag4xxTypeUnknown;
switch(version->hw_major) {
case NTAG4XX_HW_MAJOR_TYPE_413_DNA:
type = Ntag4xxType413DNA;
break;
case NTAG4XX_HW_MAJOR_TYPE_424_DNA:
if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) {
type = Ntag4xxType424DNATT;
} else {
type = Ntag4xxType424DNA;
}
break;
// TODO: there is no info online or in other implementations (NXP TagInfo, NFC Tools, Proxmark3)
// about what the HWMajorVersion is supposed to be for NTAG426Q DNA, and they don't seem to be for sale
// case NTAG4XX_HW_MAJOR_TYPE_426Q_DNA:
// if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) {
// type = Ntag4xxType426QDNATT;
// } else {
// type = Ntag4xxType426QDNA;
// }
// break;
default:
break;
}
return type;
}
const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type) {
furi_check(data);
const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version);
if(type == Ntag4xxTypeUnknown) {
furi_string_printf(data->device_name, "Unknown %s", NTAG4XX_PROTOCOL_NAME);
} else {
furi_string_printf(data->device_name, "%s", ntag4xx_type_strings[type]);
if(name_type == NfcDeviceNameTypeShort) {
furi_string_replace(data->device_name, "TagTamper", "TT");
}
}
return furi_string_get_cstr(data->device_name);
}
const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len) {
furi_check(data);
furi_check(uid_len);
return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len);
}
bool ntag4xx_set_uid(Ntag4xxData* 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* ntag4xx_get_base_data(const Ntag4xxData* data) {
furi_check(data);
return data->iso14443_4a_data;
}
+114
View File
@@ -0,0 +1,114 @@
#pragma once
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
#ifdef __cplusplus
extern "C" {
#endif
#define NTAG4XX_UID_SIZE (7)
#define NTAG4XX_BATCH_SIZE (4)
#define NTAG4XX_BATCH_EXTRA_BITS 4
#define NTAG4XX_FAB_KEY_SIZE_BITS_4 4
#define NTAG4XX_FAB_KEY_SIZE_BITS_1 1
#define NTAG4XX_PROD_WEEK_SIZE_BITS 7
#define NTAG4XX_CMD_GET_VERSION (0x60)
typedef enum {
Ntag4xxErrorNone,
Ntag4xxErrorNotPresent,
Ntag4xxErrorProtocol,
Ntag4xxErrorTimeout,
} Ntag4xxError;
typedef enum {
Ntag4xxType413DNA,
Ntag4xxType424DNA,
Ntag4xxType424DNATT,
Ntag4xxType426QDNA,
Ntag4xxType426QDNATT,
Ntag4xxTypeUnknown,
Ntag4xxTypeNum,
} Ntag4xxType;
#pragma pack(push, 1)
typedef struct {
uint8_t hw_vendor;
uint8_t hw_type;
uint8_t hw_subtype;
uint8_t hw_major;
uint8_t hw_minor;
uint8_t hw_storage;
uint8_t hw_proto;
uint8_t sw_vendor;
uint8_t sw_type;
uint8_t sw_subtype;
uint8_t sw_major;
uint8_t sw_minor;
uint8_t sw_storage;
uint8_t sw_proto;
uint8_t uid[NTAG4XX_UID_SIZE];
// [36b batch][5b fab key][7b prod week]
// 5b fab key is split 4b in last byte of batch and 1b in prod week
// Due to endianness, they appear swapped in the struct definition
uint8_t batch[NTAG4XX_BATCH_SIZE];
struct {
uint8_t fab_key_4b : NTAG4XX_FAB_KEY_SIZE_BITS_4;
uint8_t batch_extra : NTAG4XX_BATCH_EXTRA_BITS;
};
struct {
uint8_t prod_week : NTAG4XX_PROD_WEEK_SIZE_BITS;
uint8_t fab_key_1b : NTAG4XX_FAB_KEY_SIZE_BITS_1;
};
uint8_t prod_year;
struct {
uint8_t fab_key_id;
} optional;
} Ntag4xxVersion;
#pragma pack(pop)
typedef struct {
Iso14443_4aData* iso14443_4a_data;
Ntag4xxVersion version;
FuriString* device_name;
} Ntag4xxData;
extern const NfcDeviceBase nfc_device_ntag4xx;
// Virtual methods
Ntag4xxData* ntag4xx_alloc(void);
void ntag4xx_free(Ntag4xxData* data);
void ntag4xx_reset(Ntag4xxData* data);
void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other);
bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type);
bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version);
bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff);
bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other);
const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type);
const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len);
bool ntag4xx_set_uid(Ntag4xxData* data, const uint8_t* uid, size_t uid_len);
Iso14443_4aData* ntag4xx_get_base_data(const Ntag4xxData* data);
// Helpers
Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version);
#ifdef __cplusplus
}
#endif
+54
View File
@@ -0,0 +1,54 @@
#include "ntag4xx_i.h"
#define TAG "Ntag4xx"
#define NTAG4XX_FFF_VERSION_KEY \
NTAG4XX_FFF_PICC_PREFIX " " \
"Version"
Ntag4xxError ntag4xx_process_error(Iso14443_4aError error) {
switch(error) {
case Iso14443_4aErrorNone:
return Ntag4xxErrorNone;
case Iso14443_4aErrorNotPresent:
return Ntag4xxErrorNotPresent;
case Iso14443_4aErrorTimeout:
return Ntag4xxErrorTimeout;
default:
return Ntag4xxErrorProtocol;
}
}
Ntag4xxError ntag4xx_process_status_code(uint8_t status_code) {
switch(status_code) {
case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK:
return Ntag4xxErrorNone;
default:
return Ntag4xxErrorProtocol;
}
}
bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf) {
const size_t buf_size = bit_buffer_get_size_bytes(buf);
const bool can_parse = buf_size == sizeof(Ntag4xxVersion) ||
buf_size == sizeof(Ntag4xxVersion) - sizeof(data->optional);
if(can_parse) {
bit_buffer_write_bytes(buf, data, sizeof(Ntag4xxVersion));
if(buf_size < sizeof(Ntag4xxVersion)) {
memset(&data->optional, 0, sizeof(data->optional));
}
}
return can_parse && (data->hw_type & 0x0F) == 0x04;
}
bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff) {
return flipper_format_read_hex(
ff, NTAG4XX_FFF_VERSION_KEY, (uint8_t*)data, sizeof(Ntag4xxVersion));
}
bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff) {
return flipper_format_write_hex(
ff, NTAG4XX_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(Ntag4xxVersion));
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "ntag4xx.h"
#include <nfc/helpers/nxp_native_command.h>
#define NTAG4XX_FFF_PICC_PREFIX "PICC"
// Internal helpers
Ntag4xxError ntag4xx_process_error(Iso14443_4aError error);
Ntag4xxError ntag4xx_process_status_code(uint8_t status_code);
// Parse internal Ntag4xx structures
bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf);
// Load internal Ntag4xx structures
bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff);
// Save internal Ntag4xx structures
bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff);
+165
View File
@@ -0,0 +1,165 @@
#include "ntag4xx_poller_i.h"
#include <nfc/protocols/nfc_poller_base.h>
#include <furi.h>
#define TAG "Ntag4xxPoller"
#define NTAG4XX_BUF_SIZE (64U)
#define NTAG4XX_RESULT_BUF_SIZE (512U)
typedef NfcCommand (*Ntag4xxPollerReadHandler)(Ntag4xxPoller* instance);
static const Ntag4xxData* ntag4xx_poller_get_data(Ntag4xxPoller* instance) {
furi_assert(instance);
return instance->data;
}
static Ntag4xxPoller* ntag4xx_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) {
Ntag4xxPoller* instance = malloc(sizeof(Ntag4xxPoller));
instance->iso14443_4a_poller = iso14443_4a_poller;
instance->data = ntag4xx_alloc();
instance->tx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE);
instance->rx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE);
instance->input_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE);
instance->result_buffer = bit_buffer_alloc(NTAG4XX_RESULT_BUF_SIZE);
instance->ntag4xx_event.data = &instance->ntag4xx_event_data;
instance->general_event.protocol = NfcProtocolNtag4xx;
instance->general_event.event_data = &instance->ntag4xx_event;
instance->general_event.instance = instance;
return instance;
}
static void ntag4xx_poller_free(Ntag4xxPoller* instance) {
furi_assert(instance);
ntag4xx_free(instance->data);
bit_buffer_free(instance->tx_buffer);
bit_buffer_free(instance->rx_buffer);
bit_buffer_free(instance->input_buffer);
bit_buffer_free(instance->result_buffer);
free(instance);
}
static NfcCommand ntag4xx_poller_handler_idle(Ntag4xxPoller* instance) {
bit_buffer_reset(instance->input_buffer);
bit_buffer_reset(instance->result_buffer);
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 = Ntag4xxPollerStateReadVersion;
return NfcCommandContinue;
}
static NfcCommand ntag4xx_poller_handler_read_version(Ntag4xxPoller* instance) {
instance->error = ntag4xx_poller_read_version(instance, &instance->data->version);
if(instance->error == Ntag4xxErrorNone) {
FURI_LOG_D(TAG, "Read version success");
instance->state = Ntag4xxPollerStateReadSuccess;
} else {
FURI_LOG_E(TAG, "Failed to read version");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
instance->state = Ntag4xxPollerStateReadFailed;
}
return NfcCommandContinue;
}
static NfcCommand ntag4xx_poller_handler_read_failed(Ntag4xxPoller* instance) {
FURI_LOG_D(TAG, "Read Failed");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed;
instance->ntag4xx_event.data->error = instance->error;
NfcCommand command = instance->callback(instance->general_event, instance->context);
instance->state = Ntag4xxPollerStateIdle;
return command;
}
static NfcCommand ntag4xx_poller_handler_read_success(Ntag4xxPoller* instance) {
FURI_LOG_D(TAG, "Read success");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadSuccess;
NfcCommand command = instance->callback(instance->general_event, instance->context);
return command;
}
static const Ntag4xxPollerReadHandler ntag4xx_poller_read_handler[Ntag4xxPollerStateNum] = {
[Ntag4xxPollerStateIdle] = ntag4xx_poller_handler_idle,
[Ntag4xxPollerStateReadVersion] = ntag4xx_poller_handler_read_version,
[Ntag4xxPollerStateReadFailed] = ntag4xx_poller_handler_read_failed,
[Ntag4xxPollerStateReadSuccess] = ntag4xx_poller_handler_read_success,
};
static void ntag4xx_poller_set_callback(
Ntag4xxPoller* instance,
NfcGenericCallback callback,
void* context) {
furi_assert(instance);
furi_assert(callback);
instance->callback = callback;
instance->context = context;
}
static NfcCommand ntag4xx_poller_run(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_4a);
Ntag4xxPoller* 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 = ntag4xx_poller_read_handler[instance->state](instance);
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed;
command = instance->callback(instance->general_event, instance->context);
}
return command;
}
static bool ntag4xx_poller_detect(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_4a);
Ntag4xxPoller* 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) {
do {
Ntag4xxError error = ntag4xx_poller_read_version(instance, &instance->data->version);
if(error != Ntag4xxErrorNone) break;
protocol_detected = true;
} while(false);
}
return protocol_detected;
}
const NfcPollerBase ntag4xx_poller = {
.alloc = (NfcPollerAlloc)ntag4xx_poller_alloc,
.free = (NfcPollerFree)ntag4xx_poller_free,
.set_callback = (NfcPollerSetCallback)ntag4xx_poller_set_callback,
.run = (NfcPollerRun)ntag4xx_poller_run,
.detect = (NfcPollerDetect)ntag4xx_poller_detect,
.get_data = (NfcPollerGetData)ntag4xx_poller_get_data,
};
@@ -0,0 +1,43 @@
#pragma once
#include "ntag4xx.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Ntag4xxPoller opaque type definition.
*/
typedef struct Ntag4xxPoller Ntag4xxPoller;
/**
* @brief Enumeration of possible Ntag4xx poller event types.
*/
typedef enum {
Ntag4xxPollerEventTypeReadSuccess, /**< Card was read successfully. */
Ntag4xxPollerEventTypeReadFailed, /**< Poller failed to read card. */
} Ntag4xxPollerEventType;
/**
* @brief Ntag4xx poller event data.
*/
typedef union {
Ntag4xxError error; /**< Error code indicating card reading fail reason. */
} Ntag4xxPollerEventData;
/**
* @brief Ntag4xx poller event structure.
*
* Upon emission of an event, an instance of this struct will be passed to the callback.
*/
typedef struct {
Ntag4xxPollerEventType type; /**< Type of emmitted event. */
Ntag4xxPollerEventData* data; /**< Pointer to event specific data. */
} Ntag4xxPollerEvent;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,5 @@
#pragma once
#include <nfc/protocols/nfc_poller_base.h>
extern const NfcPollerBase ntag4xx_poller;
@@ -0,0 +1,52 @@
#include "ntag4xx_poller_i.h"
#include <furi.h>
#include "ntag4xx_i.h"
#define TAG "Ntag4xxPoller"
Ntag4xxError ntag4xx_poller_send_chunks(
Ntag4xxPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer) {
furi_check(instance);
NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK;
Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller(
instance->iso14443_4a_poller,
&status_code,
tx_buffer,
rx_buffer,
NxpNativeCommandModeIsoWrapped,
instance->tx_buffer,
instance->rx_buffer);
if(iso14443_4a_error != Iso14443_4aErrorNone) {
return ntag4xx_process_error(iso14443_4a_error);
}
return ntag4xx_process_status_code(status_code);
}
Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data) {
furi_check(instance);
bit_buffer_reset(instance->input_buffer);
bit_buffer_append_byte(instance->input_buffer, NTAG4XX_CMD_GET_VERSION);
Ntag4xxError error;
do {
error =
ntag4xx_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
if(error != Ntag4xxErrorNone) break;
if(!ntag4xx_version_parse(data, instance->result_buffer)) {
error = Ntag4xxErrorProtocol;
}
} while(false);
return error;
}
@@ -0,0 +1,40 @@
#pragma once
#include "ntag4xx_poller.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
Ntag4xxPollerStateIdle,
Ntag4xxPollerStateReadVersion,
Ntag4xxPollerStateReadFailed,
Ntag4xxPollerStateReadSuccess,
Ntag4xxPollerStateNum,
} Ntag4xxPollerState;
struct Ntag4xxPoller {
Iso14443_4aPoller* iso14443_4a_poller;
Ntag4xxPollerState state;
Ntag4xxError error;
Ntag4xxData* data;
BitBuffer* tx_buffer;
BitBuffer* rx_buffer;
BitBuffer* input_buffer;
BitBuffer* result_buffer;
Ntag4xxPollerEventData ntag4xx_event_data;
Ntag4xxPollerEvent ntag4xx_event;
NfcGenericEvent general_event;
NfcGenericCallback callback;
void* context;
};
Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data);
#ifdef __cplusplus
}
#endif
+170
View File
@@ -0,0 +1,170 @@
#include "type_4_tag_i.h"
#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->device_name = furi_string_alloc();
data->platform_name = furi_string_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);
furi_string_free(data->platform_name);
furi_string_free(data->device_name);
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->is_tag_specific = false;
furi_string_reset(data->device_name);
furi_string_reset(data->platform_name);
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->is_tag_specific = other->is_tag_specific;
furi_string_set(data->device_name, other->device_name);
furi_string_set(data->platform_name, other->platform_name);
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;
if(!type_4_tag_ndef_data_load(data, ff)) 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;
if(!type_4_tag_ndef_data_save(data, ff)) 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->is_tag_specific == other->is_tag_specific &&
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;
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE (2048U - sizeof(uint16_t))
typedef enum {
Type4TagErrorNone,
Type4TagErrorNotPresent,
Type4TagErrorProtocol,
Type4TagErrorTimeout,
Type4TagErrorWrongFormat,
Type4TagErrorNotSupported,
Type4TagErrorApduFailed,
Type4TagErrorCardUnformatted,
Type4TagErrorCardLocked,
Type4TagErrorCustomCommand,
} Type4TagError;
typedef enum {
Type4TagPlatformUnknown,
Type4TagPlatformNtag4xx,
Type4TagPlatformMfDesfire,
} Type4TagPlatform;
typedef struct {
Iso14443_4aData* iso14443_4a_data;
FuriString* device_name;
// Tag specific data
bool is_tag_specific;
Type4TagPlatform platform;
FuriString* platform_name;
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
+163
View File
@@ -0,0 +1,163 @@
#include "type_4_tag_i.h"
#include <bit_lib/bit_lib.h>
#define TAG "Type4Tag"
#define TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY "NDEF Data Size"
#define TYPE_4_TAG_FFF_NDEF_DATA_KEY "NDEF Data"
#define TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE (16U)
const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_MF_NAME};
const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_DF_NAME};
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;
}
}
void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len) {
furi_check(len >= TYPE_4_TAG_T4T_CC_MIN_SIZE);
Type4TagCc* cc = (Type4TagCc*)buf;
bit_lib_num_to_bytes_be(TYPE_4_TAG_T4T_CC_MIN_SIZE, sizeof(cc->len), (void*)&cc->len);
cc->t4t_vno = TYPE_4_TAG_T4T_CC_VNO;
bit_lib_num_to_bytes_be(
data->is_tag_specific ? MIN(data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) :
TYPE_4_TAG_CHUNK_LEN,
sizeof(cc->mle),
(void*)&cc->mle);
bit_lib_num_to_bytes_be(
data->is_tag_specific ? MIN(data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) :
TYPE_4_TAG_CHUNK_LEN,
sizeof(cc->mlc),
(void*)&cc->mlc);
cc->tlv[0].type = Type4TagCcTlvTypeNdefFileCtrl;
cc->tlv[0].len = sizeof(cc->tlv[0].value.ndef_file_ctrl);
bit_lib_num_to_bytes_be(
data->is_tag_specific ? data->ndef_file_id : TYPE_4_TAG_T4T_NDEF_EF_ID,
sizeof(cc->tlv[0].value.ndef_file_ctrl.file_id),
(void*)&cc->tlv[0].value.ndef_file_ctrl.file_id);
bit_lib_num_to_bytes_be(
sizeof(uint16_t) +
(data->is_tag_specific ? data->ndef_max_len : TYPE_4_TAG_DEFAULT_NDEF_SIZE),
sizeof(cc->tlv[0].value.ndef_file_ctrl.max_len),
(void*)&cc->tlv[0].value.ndef_file_ctrl.max_len);
cc->tlv[0].value.ndef_file_ctrl.read_perm =
data->is_tag_specific ? data->ndef_read_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE;
cc->tlv[0].value.ndef_file_ctrl.write_perm =
data->is_tag_specific ? data->ndef_write_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE;
}
Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len) {
if(len < TYPE_4_TAG_T4T_CC_MIN_SIZE) {
FURI_LOG_E(TAG, "Unsupported T4T version");
return Type4TagErrorWrongFormat;
}
const Type4TagCc* cc = (const Type4TagCc*)buf;
if(cc->t4t_vno != TYPE_4_TAG_T4T_CC_VNO) {
FURI_LOG_E(TAG, "Unsupported T4T version");
return Type4TagErrorNotSupported;
}
const Type4TagCcTlv* tlv = cc->tlv;
const Type4TagCcTlvNdefFileCtrl* ndef_file_ctrl = NULL;
const void* end = MIN((void*)cc + cc->len, (void*)cc + len);
while((void*)tlv < end) {
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");
return Type4TagErrorWrongFormat;
}
data->t4t_version.value = cc->t4t_vno;
data->chunk_max_read = bit_lib_bytes_to_num_be((void*)&cc->mle, sizeof(cc->mle));
data->chunk_max_write = bit_lib_bytes_to_num_be((void*)&cc->mlc, sizeof(cc->mlc));
data->ndef_file_id =
bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->file_id, sizeof(ndef_file_ctrl->file_id));
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);
data->ndef_read_lock = ndef_file_ctrl->read_perm;
data->ndef_write_lock = ndef_file_ctrl->write_perm;
return Type4TagErrorNone;
}
bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff) {
uint32_t ndef_data_size;
if(!flipper_format_read_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) {
return false;
}
if(ndef_data_size == 0) {
return true;
}
simple_array_init(data->ndef_data, ndef_data_size);
uint32_t ndef_data_pos = 0;
uint8_t* ndef_data = simple_array_get_data(data->ndef_data);
while(ndef_data_size > 0) {
uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE);
if(!flipper_format_read_hex(
ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) {
simple_array_reset(data->ndef_data);
return false;
}
ndef_data_pos += ndef_line_size;
ndef_data_size -= ndef_line_size;
}
return true;
}
bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff) {
uint32_t ndef_data_size = simple_array_get_count(data->ndef_data);
if(!flipper_format_write_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) {
return false;
}
if(ndef_data_size == 0) {
return true;
}
uint32_t ndef_data_pos = 0;
uint8_t* ndef_data = simple_array_get_data(data->ndef_data);
while(ndef_data_size > 0) {
uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE);
if(!flipper_format_write_hex(
ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) {
return false;
}
ndef_data_pos += ndef_line_size;
ndef_data_size -= ndef_line_size;
}
return true;
}
+110
View File
@@ -0,0 +1,110 @@
#pragma once
#include "type_4_tag.h"
// ISO SELECT FILE command and parameters
#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_EF_ID (0x02)
#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)
// ISO READ BINARY command and parameters
#define TYPE_4_TAG_ISO_READ_CMD 0x00, 0xB0
#define TYPE_4_TAG_ISO_READ_P1_ID_MASK (1 << 7)
#define TYPE_4_TAG_ISO_READ_P_BEGINNING 0x00, 0x00
#define TYPE_4_TAG_ISO_READ_P_OFFSET_MAX (32767U)
#define TYPE_4_TAG_ISO_READ_LE_FULL (0x00)
// ISO UPDATE BINARY command and parameters
#define TYPE_4_TAG_ISO_WRITE_CMD 0x00, 0xD6
#define TYPE_4_TAG_ISO_WRITE_P1_ID_MASK (1 << 7)
#define TYPE_4_TAG_ISO_WRITE_P_BEGINNING 0x00, 0x00
#define TYPE_4_TAG_ISO_WRITE_LE_EMPTY (0x00)
// Common APDU parameters and values
#define TYPE_4_TAG_ISO_STATUS_LEN (2U)
#define TYPE_4_TAG_ISO_STATUS_SUCCESS 0x90, 0x00
#define TYPE_4_TAG_ISO_STATUS_OFFSET_ERR 0x6B, 0x00
#define TYPE_4_TAG_ISO_STATUS_NOT_FOUND 0x6A, 0x82
#define TYPE_4_TAG_ISO_STATUS_NO_SUPPORT 0x6A, 0x81
#define TYPE_4_TAG_ISO_STATUS_BAD_PARAMS 0x6A, 0x86
#define TYPE_4_TAG_ISO_STATUS_NO_CMD 0x68, 0x00
#define TYPE_4_TAG_ISO_RW_CHUNK_LEN (255U)
// Common IDs and Names, note:
// MF = Master File (PICC/Card Level)
// DF = Dedicated File (Application)
// EF = Elementary File (File)
#define TYPE_4_TAG_ISO_NAME_LEN (7U)
#define TYPE_4_TAG_ISO_MF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x00
#define TYPE_4_TAG_ISO_DF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01
#define TYPE_4_TAG_ISO_ID_LEN (2U)
#define TYPE_4_TAG_ISO_MF_ID (0x3F00)
#define TYPE_4_TAG_ISO_DF_ID (0xE110)
#define TYPE_4_TAG_T4T_CC_EF_ID (0xE103)
#define TYPE_4_TAG_T4T_NDEF_EF_ID (0xE104)
// Capability Container parsing parameters
#define TYPE_4_TAG_T4T_CC_VNO (0x20)
#define TYPE_4_TAG_T4T_CC_RW_LOCK_NONE (0x00)
#define TYPE_4_TAG_T4T_CC_MIN_SIZE (sizeof(Type4TagCc) + sizeof(Type4TagCcTlv))
// Implementation-specific sizes and defaults
// 4a layer adds 1..3 byte prefix, 3a layer adds 2 byte suffix and has 256 byte buffer
#define TYPE_4_TAG_BUF_SIZE (256U - 3U - 2U)
// Read returns 2 byte status trailer, write sends 5 byte command header
#define TYPE_4_TAG_CHUNK_LEN MIN(TYPE_4_TAG_BUF_SIZE - 5U, TYPE_4_TAG_ISO_RW_CHUNK_LEN)
#define TYPE_4_TAG_DEFAULT_NDEF_SIZE TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE
extern const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN];
extern const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN];
// Capability Container parsing structures
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;
// Internal helpers
Type4TagError type_4_tag_process_error(Iso14443_4aError error);
void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len);
Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len);
// Load internal Type4Tag structures
bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff);
// Save internal Type4Tag structures
bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff);
@@ -0,0 +1,88 @@
#include "type_4_tag_listener_i.h"
#include "type_4_tag_listener_defs.h"
#include "type_4_tag_i.h"
#define TAG "Type4TagListener"
static void type_4_tag_listener_reset_state(Type4TagListener* instance) {
instance->state = Type4TagListenerStateIdle;
}
static Type4TagListener*
type_4_tag_listener_alloc(Iso14443_4aListener* iso14443_4a_listener, Type4TagData* data) {
furi_assert(iso14443_4a_listener);
Type4TagListener* instance = malloc(sizeof(Type4TagListener));
instance->iso14443_4a_listener = iso14443_4a_listener;
instance->data = data;
instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE);
instance->type_4_tag_event.data = &instance->type_4_tag_event_data;
instance->generic_event.protocol = NfcProtocolType4Tag;
instance->generic_event.instance = instance;
instance->generic_event.event_data = &instance->type_4_tag_event;
return instance;
}
static void type_4_tag_listener_free(Type4TagListener* instance) {
furi_assert(instance);
furi_assert(instance->data);
furi_assert(instance->tx_buffer);
bit_buffer_free(instance->tx_buffer);
free(instance);
}
static void type_4_tag_listener_set_callback(
Type4TagListener* instance,
NfcGenericCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->context = context;
}
static const Type4TagData* type_4_tag_listener_get_data(Type4TagListener* instance) {
furi_assert(instance);
furi_assert(instance->data);
return instance->data;
}
static NfcCommand type_4_tag_listener_run(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolIso15693_3);
furi_assert(event.event_data);
Type4TagListener* instance = context;
Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data;
BitBuffer* rx_buffer = iso14443_4a_event->data->buffer;
NfcCommand command = NfcCommandContinue;
if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeFieldOff) {
type_4_tag_listener_reset_state(instance);
command = NfcCommandSleep;
} else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeHalted) {
type_4_tag_listener_reset_state(instance);
} else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) {
const Type4TagError error = type_4_tag_listener_handle_apdu(instance, rx_buffer);
if(error == Type4TagErrorCustomCommand && instance->callback) {
instance->type_4_tag_event.type = Type4TagListenerEventTypeCustomCommand;
instance->type_4_tag_event.data->buffer = rx_buffer;
command = instance->callback(instance->generic_event, instance->context);
}
}
return command;
}
const NfcListenerBase nfc_listener_type_4_tag = {
.alloc = (NfcListenerAlloc)type_4_tag_listener_alloc,
.free = (NfcListenerFree)type_4_tag_listener_free,
.set_callback = (NfcListenerSetCallback)type_4_tag_listener_set_callback,
.get_data = (NfcListenerGetData)type_4_tag_listener_get_data,
.run = (NfcListenerRun)type_4_tag_listener_run,
};
@@ -0,0 +1,26 @@
#pragma once
#include "type_4_tag.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Type4TagListener Type4TagListener;
typedef enum {
Type4TagListenerEventTypeCustomCommand,
} Type4TagListenerEventType;
typedef struct {
BitBuffer* buffer;
} Type4TagListenerEventData;
typedef struct {
Type4TagListenerEventType type;
Type4TagListenerEventData* data;
} Type4TagListenerEvent;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,5 @@
#pragma once
#include <nfc/protocols/nfc_listener_base.h>
extern const NfcListenerBase nfc_listener_type_4_tag;
@@ -0,0 +1,382 @@
#include "type_4_tag_listener_i.h"
#include "type_4_tag_i.h"
#include <bit_lib/bit_lib.h>
#define TAG "Type4TagListener"
typedef Type4TagError (*Type4TagListenerApduHandler)(
Type4TagListener* instance,
uint8_t p1,
uint8_t p2,
size_t lc,
const uint8_t* data,
size_t le);
typedef struct {
uint8_t cla_ins[2];
Type4TagListenerApduHandler handler;
} Type4TagListenerApduCommand;
static const uint8_t type_4_tag_success_apdu[] = {TYPE_4_TAG_ISO_STATUS_SUCCESS};
static const uint8_t type_4_tag_offset_error_apdu[] = {TYPE_4_TAG_ISO_STATUS_OFFSET_ERR};
static const uint8_t type_4_tag_not_found_apdu[] = {TYPE_4_TAG_ISO_STATUS_NOT_FOUND};
static const uint8_t type_4_tag_no_support_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_SUPPORT};
static const uint8_t type_4_tag_bad_params_apdu[] = {TYPE_4_TAG_ISO_STATUS_BAD_PARAMS};
static const uint8_t type_4_tag_no_cmd_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_CMD};
static Type4TagError type_4_tag_listener_iso_select(
Type4TagListener* instance,
uint8_t p1,
uint8_t p2,
size_t lc,
const uint8_t* data,
size_t le) {
UNUSED(p2);
UNUSED(le);
if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_NAME && lc == TYPE_4_TAG_ISO_NAME_LEN) {
if(memcmp(type_4_tag_iso_mf_name, data, sizeof(type_4_tag_iso_mf_name)) == 0) {
instance->state = Type4TagListenerStateSelectedPicc;
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
if(memcmp(type_4_tag_iso_df_name, data, sizeof(type_4_tag_iso_df_name)) == 0) {
instance->state = Type4TagListenerStateSelectedApplication;
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
} else if(
(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID || p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID) &&
lc == TYPE_4_TAG_ISO_ID_LEN) {
uint16_t id = bit_lib_bytes_to_num_be(data, sizeof(uint16_t));
if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID) {
if(id == TYPE_4_TAG_ISO_MF_ID) {
instance->state = Type4TagListenerStateSelectedPicc;
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
if(id == TYPE_4_TAG_ISO_DF_ID) {
instance->state = Type4TagListenerStateSelectedApplication;
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
}
if(instance->state >= Type4TagListenerStateSelectedApplication) {
if(id == TYPE_4_TAG_T4T_CC_EF_ID) {
instance->state = Type4TagListenerStateSelectedCapabilityContainer;
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
if(id == (instance->data->is_tag_specific ? instance->data->ndef_file_id :
TYPE_4_TAG_T4T_NDEF_EF_ID)) {
instance->state = Type4TagListenerStateSelectedNdefMessage;
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
}
}
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu));
return Type4TagErrorCustomCommand;
}
static Type4TagError type_4_tag_listener_iso_read(
Type4TagListener* instance,
uint8_t p1,
uint8_t p2,
size_t lc,
const uint8_t* data,
size_t le) {
UNUSED(lc);
UNUSED(data);
size_t offset;
if(p1 & TYPE_4_TAG_ISO_READ_P1_ID_MASK) {
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu));
return Type4TagErrorCustomCommand;
} else {
offset = (p1 << 8) + p2;
}
if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) {
uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE];
if(offset >= sizeof(cc_buf)) {
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_offset_error_apdu,
sizeof(type_4_tag_offset_error_apdu));
return Type4TagErrorWrongFormat;
}
type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf));
bit_buffer_append_bytes(
instance->tx_buffer, cc_buf + offset, MIN(sizeof(cc_buf) - offset, le));
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
if(instance->state == Type4TagListenerStateSelectedNdefMessage) {
size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data);
bool included_len = false;
if(offset < sizeof(uint16_t)) {
uint8_t ndef_file_len_be[sizeof(uint16_t)];
bit_lib_num_to_bytes_be(ndef_file_len, sizeof(ndef_file_len_be), ndef_file_len_be);
uint8_t read_len = MIN(sizeof(ndef_file_len_be) - offset, le);
bit_buffer_append_bytes(instance->tx_buffer, &ndef_file_len_be[offset], read_len);
included_len = true;
offset = sizeof(uint16_t);
le -= read_len;
}
offset -= sizeof(uint16_t);
if(offset >= ndef_file_len) {
if(included_len) {
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
} else {
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_offset_error_apdu,
sizeof(type_4_tag_offset_error_apdu));
return Type4TagErrorWrongFormat;
}
}
const uint8_t* ndef_data = simple_array_cget_data(instance->data->ndef_data);
bit_buffer_append_bytes(
instance->tx_buffer, &ndef_data[offset], MIN(ndef_file_len - offset, le));
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu));
return Type4TagErrorCustomCommand;
}
static Type4TagError type_4_tag_listener_iso_write(
Type4TagListener* instance,
uint8_t p1,
uint8_t p2,
size_t lc,
const uint8_t* data,
size_t le) {
UNUSED(le);
size_t offset;
if(p1 & TYPE_4_TAG_ISO_WRITE_P1_ID_MASK) {
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu));
return Type4TagErrorCustomCommand;
} else {
offset = (p1 << 8) + p2;
}
if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) {
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu));
return Type4TagErrorNotSupported;
}
if(instance->state == Type4TagListenerStateSelectedNdefMessage) {
if(offset + lc > sizeof(uint16_t) + (instance->data->is_tag_specific ?
instance->data->ndef_max_len :
TYPE_4_TAG_DEFAULT_NDEF_SIZE)) {
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_offset_error_apdu,
sizeof(type_4_tag_offset_error_apdu));
return Type4TagErrorWrongFormat;
}
const size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data);
size_t ndef_file_len_new = ndef_file_len;
if(offset < sizeof(uint16_t)) {
const uint8_t write_len = sizeof(uint16_t) - offset;
ndef_file_len_new = bit_lib_bytes_to_num_be(data, write_len);
offset = sizeof(uint16_t);
data += offset;
lc -= write_len;
}
offset -= sizeof(uint16_t);
ndef_file_len_new = MAX(ndef_file_len_new, offset + lc);
if(ndef_file_len_new != ndef_file_len) {
SimpleArray* ndef_data_temp = simple_array_alloc(&simple_array_config_uint8_t);
if(ndef_file_len_new > 0) {
simple_array_init(ndef_data_temp, ndef_file_len_new);
if(ndef_file_len > 0) {
memcpy(
simple_array_get_data(ndef_data_temp),
simple_array_get_data(instance->data->ndef_data),
MIN(ndef_file_len_new, ndef_file_len));
}
}
simple_array_copy(instance->data->ndef_data, ndef_data_temp);
simple_array_free(ndef_data_temp);
}
if(ndef_file_len_new > 0 && lc > 0) {
uint8_t* ndef_data = simple_array_get_data(instance->data->ndef_data);
memcpy(&ndef_data[offset], data, lc);
}
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
return Type4TagErrorNone;
}
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu));
return Type4TagErrorCustomCommand;
}
static const Type4TagListenerApduCommand type_4_tag_listener_commands[] = {
{
.cla_ins = {TYPE_4_TAG_ISO_SELECT_CMD},
.handler = type_4_tag_listener_iso_select,
},
{
.cla_ins = {TYPE_4_TAG_ISO_READ_CMD},
.handler = type_4_tag_listener_iso_read,
},
{
.cla_ins = {TYPE_4_TAG_ISO_WRITE_CMD},
.handler = type_4_tag_listener_iso_write,
},
};
Type4TagError
type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer) {
Type4TagError error = Type4TagErrorNone;
bit_buffer_reset(instance->tx_buffer);
do {
typedef struct {
uint8_t cla_ins[2];
uint8_t p1;
uint8_t p2;
uint8_t body[];
} Type4TagApdu;
const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer);
if(buf_size < sizeof(Type4TagApdu)) {
error = Type4TagErrorWrongFormat;
break;
}
const Type4TagApdu* apdu = (const Type4TagApdu*)bit_buffer_get_data(rx_buffer);
Type4TagListenerApduHandler handler = NULL;
for(size_t i = 0; i < COUNT_OF(type_4_tag_listener_commands); i++) {
const Type4TagListenerApduCommand* command = &type_4_tag_listener_commands[i];
if(memcmp(apdu->cla_ins, command->cla_ins, sizeof(apdu->cla_ins)) == 0) {
handler = command->handler;
break;
}
}
if(!handler) {
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_no_cmd_apdu, sizeof(type_4_tag_no_cmd_apdu));
error = Type4TagErrorCustomCommand;
break;
}
size_t body_size = buf_size - offsetof(Type4TagApdu, body);
size_t lc;
const uint8_t* data = apdu->body;
size_t le;
if(body_size == 0) {
lc = 0;
data = NULL;
le = 0;
} else if(body_size == 1) {
lc = 0;
data = NULL;
le = apdu->body[0];
if(le == 0) {
le = 256;
}
} else if(body_size == 3 && apdu->body[0] == 0) {
lc = 0;
data = NULL;
le = bit_lib_bytes_to_num_be(&apdu->body[1], sizeof(uint16_t));
if(le == 0) {
le = 65536;
}
} else {
bool extended_lc = false;
if(data[0] == 0) {
extended_lc = true;
lc = bit_lib_bytes_to_num_be(&data[1], sizeof(uint16_t));
data += 1 + sizeof(uint16_t);
body_size -= 1 + sizeof(uint16_t);
} else {
lc = data[0];
data++;
body_size--;
}
if(lc == 0 || body_size < lc) {
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_bad_params_apdu,
sizeof(type_4_tag_bad_params_apdu));
error = Type4TagErrorWrongFormat;
break;
}
if(body_size == lc) {
le = 0;
} else if(!extended_lc && body_size - lc == 1) {
le = data[lc];
if(le == 0) {
le = 256;
}
} else if(extended_lc && body_size - lc == 2) {
le = bit_lib_bytes_to_num_be(&data[lc], sizeof(uint16_t));
if(le == 0) {
le = 65536;
}
} else {
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_bad_params_apdu,
sizeof(type_4_tag_bad_params_apdu));
error = Type4TagErrorWrongFormat;
break;
}
}
error = handler(instance, apdu->p1, apdu->p2, lc, data, le);
} while(false);
if(bit_buffer_get_size_bytes(instance->tx_buffer) > 0) {
const Iso14443_4aError iso14443_4a_error =
iso14443_4a_listener_send_block(instance->iso14443_4a_listener, instance->tx_buffer);
// Keep error flag to show unknown command on screen
if(error != Type4TagErrorCustomCommand) {
error = type_4_tag_process_error(iso14443_4a_error);
}
}
return error;
}
@@ -0,0 +1,38 @@
#pragma once
#include "type_4_tag_listener.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
Type4TagListenerStateIdle,
Type4TagListenerStateSelectedPicc,
Type4TagListenerStateSelectedApplication,
Type4TagListenerStateSelectedCapabilityContainer,
Type4TagListenerStateSelectedNdefMessage,
} Type4TagListenerState;
struct Type4TagListener {
Iso14443_4aListener* iso14443_4a_listener;
Type4TagData* data;
Type4TagListenerState state;
BitBuffer* tx_buffer;
NfcGenericEvent generic_event;
Type4TagListenerEvent type_4_tag_event;
Type4TagListenerEventData type_4_tag_event_data;
NfcGenericCallback callback;
void* context;
};
Type4TagError
type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,289 @@
#include "type_4_tag_poller_i.h"
#include "type_4_tag_poller_defs.h"
#include "type_4_tag_i.h"
#define TAG "Type4TagPoller"
typedef NfcCommand (*Type4TagPollerReadHandler)(Type4TagPoller* instance);
static 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 = Type4TagPollerStateRequestMode;
return NfcCommandContinue;
}
static NfcCommand type_4_tag_poller_handler_request_mode(Type4TagPoller* instance) {
NfcCommand command = NfcCommandContinue;
instance->type_4_tag_event.type = Type4TagPollerEventTypeRequestMode;
instance->type_4_tag_event.data->poller_mode.mode = Type4TagPollerModeRead;
instance->type_4_tag_event.data->poller_mode.data = NULL;
command = instance->callback(instance->general_event, instance->context);
instance->mode = instance->type_4_tag_event.data->poller_mode.mode;
if(instance->mode == Type4TagPollerModeWrite) {
type_4_tag_copy(instance->data, instance->type_4_tag_event.data->poller_mode.data);
}
instance->state = Type4TagPollerStateDetectPlatform;
return command;
}
static NfcCommand type_4_tag_poller_handler_detect_platform(Type4TagPoller* instance) {
instance->error = type_4_tag_poller_detect_platform(instance);
if(instance->error == Type4TagErrorNone) {
FURI_LOG_D(TAG, "Detect platform success");
} else {
FURI_LOG_W(TAG, "Failed to detect platform");
}
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");
if(instance->mode == Type4TagPollerModeWrite &&
instance->error == Type4TagErrorCardUnformatted) {
instance->state = Type4TagPollerStateCreateApplication;
} else {
instance->state = Type4TagPollerStateFailed;
}
}
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 = instance->mode == Type4TagPollerModeRead ?
Type4TagPollerStateReadNdefMessage :
Type4TagPollerStateWriteNdefMessage;
} else {
FURI_LOG_E(TAG, "Failed to read CC");
if(instance->mode == Type4TagPollerModeWrite &&
instance->error == Type4TagErrorCardUnformatted) {
instance->state = Type4TagPollerStateCreateCapabilityContainer;
} else {
instance->state = Type4TagPollerStateFailed;
}
}
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 = Type4TagPollerStateSuccess;
} else {
FURI_LOG_E(TAG, "Failed to read NDEF");
instance->state = Type4TagPollerStateFailed;
}
return NfcCommandContinue;
}
static NfcCommand type_4_tag_poller_handler_create_app(Type4TagPoller* instance) {
instance->error = type_4_tag_poller_create_app(instance);
if(instance->error == Type4TagErrorNone) {
FURI_LOG_D(TAG, "Create application success");
instance->state = Type4TagPollerStateSelectApplication;
} else {
FURI_LOG_E(TAG, "Failed to create application");
instance->state = Type4TagPollerStateFailed;
}
return NfcCommandContinue;
}
static NfcCommand type_4_tag_poller_handler_create_cc(Type4TagPoller* instance) {
instance->error = type_4_tag_poller_create_cc(instance);
if(instance->error == Type4TagErrorNone) {
FURI_LOG_D(TAG, "Create CC success");
instance->state = Type4TagPollerStateReadCapabilityContainer;
} else {
FURI_LOG_E(TAG, "Failed to create CC");
instance->state = Type4TagPollerStateFailed;
}
return NfcCommandContinue;
}
static NfcCommand type_4_tag_poller_handler_create_ndef(Type4TagPoller* instance) {
instance->error = type_4_tag_poller_create_ndef(instance);
if(instance->error == Type4TagErrorNone) {
FURI_LOG_D(TAG, "Create NDEF success");
instance->state = Type4TagPollerStateWriteNdefMessage;
} else {
FURI_LOG_E(TAG, "Failed to create NDEF");
instance->state = Type4TagPollerStateFailed;
}
return NfcCommandContinue;
}
static NfcCommand type_4_tag_poller_handler_write_ndef(Type4TagPoller* instance) {
instance->error = type_4_tag_poller_write_ndef(instance);
if(instance->error == Type4TagErrorNone) {
FURI_LOG_D(TAG, "Write NDEF success");
instance->state = Type4TagPollerStateSuccess;
} else {
FURI_LOG_E(TAG, "Failed to write NDEF");
if(instance->mode == Type4TagPollerModeWrite &&
instance->error == Type4TagErrorCardUnformatted) {
instance->state = Type4TagPollerStateCreateNdefMessage;
} else {
instance->state = Type4TagPollerStateFailed;
}
}
return NfcCommandContinue;
}
static NfcCommand type_4_tag_poller_handler_failed(Type4TagPoller* instance) {
FURI_LOG_D(TAG, "Operation Failed");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ?
Type4TagPollerEventTypeReadFailed :
Type4TagPollerEventTypeWriteFail;
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_success(Type4TagPoller* instance) {
FURI_LOG_D(TAG, "Operation succeeded");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ?
Type4TagPollerEventTypeReadSuccess :
Type4TagPollerEventTypeWriteSuccess;
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,
[Type4TagPollerStateRequestMode] = type_4_tag_poller_handler_request_mode,
[Type4TagPollerStateDetectPlatform] = type_4_tag_poller_handler_detect_platform,
[Type4TagPollerStateSelectApplication] = type_4_tag_poller_handler_select_app,
[Type4TagPollerStateReadCapabilityContainer] = type_4_tag_poller_handler_read_cc,
[Type4TagPollerStateReadNdefMessage] = type_4_tag_poller_handler_read_ndef,
[Type4TagPollerStateCreateApplication] = type_4_tag_poller_handler_create_app,
[Type4TagPollerStateCreateCapabilityContainer] = type_4_tag_poller_handler_create_cc,
[Type4TagPollerStateCreateNdefMessage] = type_4_tag_poller_handler_create_ndef,
[Type4TagPollerStateWriteNdefMessage] = type_4_tag_poller_handler_write_ndef,
[Type4TagPollerStateFailed] = type_4_tag_poller_handler_failed,
[Type4TagPollerStateSuccess] = type_4_tag_poller_handler_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;
instance->type_4_tag_event.data->error =
type_4_tag_process_error(iso14443_4a_event->data->error);
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,
};
@@ -0,0 +1,63 @@
#pragma once
#include "type_4_tag.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type4TagPoller opaque type definition.
*/
typedef struct Type4TagPoller Type4TagPoller;
/**
* @brief Enumeration of possible Type4Tag poller event types.
*/
typedef enum {
Type4TagPollerEventTypeRequestMode, /**< Poller requests for operating mode. */
Type4TagPollerEventTypeReadSuccess, /**< Card was read successfully. */
Type4TagPollerEventTypeReadFailed, /**< Poller failed to read card. */
Type4TagPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */
Type4TagPollerEventTypeWriteFail, /**< Poller failed to write card. */
} Type4TagPollerEventType;
/**
* @brief Enumeration of possible Type4Tag poller operating modes.
*/
typedef enum {
Type4TagPollerModeRead, /**< Poller will only read card. It's a default mode. */
Type4TagPollerModeWrite, /**< Poller will write already saved card to another presented card. */
} Type4TagPollerMode;
/**
* @brief Type4Tag poller request mode event data.
*
* This instance of this structure must be filled on Type4TagPollerEventTypeRequestMode event.
*/
typedef struct {
Type4TagPollerMode mode; /**< Mode to be used by poller. */
const Type4TagData* data; /**< Data to be used by poller. */
} Type4TagPollerEventDataRequestMode;
/**
* @brief Type4Tag poller event data.
*/
typedef union {
Type4TagError error; /**< Error code indicating card reading fail reason. */
Type4TagPollerEventDataRequestMode poller_mode; /**< Poller mode context. */
} 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
@@ -0,0 +1,5 @@
#pragma once
#include <nfc/protocols/nfc_poller_base.h>
extern const NfcPollerBase type_4_tag_poller;
@@ -0,0 +1,501 @@
#include "type_4_tag_poller_i.h"
#include "type_4_tag_i.h"
#include <bit_lib/bit_lib.h>
#include <nfc/nfc_device.h>
#include <nfc/protocols/ntag4xx/ntag4xx_poller.h>
#include <nfc/protocols/ntag4xx/ntag4xx_poller_defs.h>
#include <nfc/protocols/mf_desfire/mf_desfire_poller.h>
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
#define TAG "Type4TagPoller"
static const MfDesfireApplicationId mf_des_picc_app_id = {.data = {0x00, 0x00, 0x00}};
static const MfDesfireApplicationId mf_des_t4t_app_id = {.data = {0x10, 0xEE, 0xEE}};
static const MfDesfireKeySettings mf_des_t4t_app_key_settings = {
.is_master_key_changeable = true,
.is_free_directory_list = true,
.is_free_create_delete = true,
.is_config_changeable = true,
.change_key_id = 0,
.max_keys = 1,
.flags = 0,
};
#define MF_DES_T4T_CC_FILE_ID (0x01)
static const MfDesfireFileSettings mf_des_t4t_cc_file = {
.type = MfDesfireFileTypeStandard,
.comm = MfDesfireFileCommunicationSettingsPlaintext,
.access_rights[0] = 0xEEEE,
.access_rights_len = 1,
.data.size = TYPE_4_TAG_T4T_CC_MIN_SIZE,
};
#define MF_DES_T4T_NDEF_FILE_ID (0x02)
static const MfDesfireFileSettings mf_des_t4t_ndef_file_default = {
.type = MfDesfireFileTypeStandard,
.comm = MfDesfireFileCommunicationSettingsPlaintext,
.access_rights[0] = 0xEEE0,
.access_rights_len = 1,
};
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 Type4TagErrorWrongFormat;
}
static 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: %02X%02X", status[0], status[1]);
return Type4TagErrorApduFailed;
}
}
static Type4TagError type_4_tag_poller_iso_select_name(
Type4TagPoller* instance,
const uint8_t* name,
uint8_t name_len) {
static const uint8_t type_4_tag_iso_select_name_apdu[] = {
TYPE_4_TAG_ISO_SELECT_CMD,
TYPE_4_TAG_ISO_SELECT_P1_BY_NAME,
TYPE_4_TAG_ISO_SELECT_P2_EMPTY,
};
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_iso_select_name_apdu,
sizeof(type_4_tag_iso_select_name_apdu));
bit_buffer_append_byte(instance->tx_buffer, name_len);
bit_buffer_append_bytes(instance->tx_buffer, name, name_len);
Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer);
if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted;
return error;
}
static Type4TagError
type_4_tag_poller_iso_select_file(Type4TagPoller* instance, uint16_t file_id) {
static const uint8_t type_4_tag_iso_select_file_apdu[] = {
TYPE_4_TAG_ISO_SELECT_CMD,
TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID,
TYPE_4_TAG_ISO_SELECT_P2_EMPTY,
sizeof(file_id),
};
uint8_t file_id_be[sizeof(file_id)];
bit_lib_num_to_bytes_be(file_id, sizeof(file_id), file_id_be);
bit_buffer_append_bytes(
instance->tx_buffer,
type_4_tag_iso_select_file_apdu,
sizeof(type_4_tag_iso_select_file_apdu));
bit_buffer_append_bytes(instance->tx_buffer, file_id_be, sizeof(file_id_be));
Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer);
if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted;
return error;
}
static Type4TagError type_4_tag_poller_iso_read(
Type4TagPoller* instance,
uint16_t offset,
uint16_t length,
uint8_t* buffer) {
const uint8_t chunk_max = instance->data->is_tag_specific ?
MIN(instance->data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) :
TYPE_4_TAG_CHUNK_LEN;
if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) {
FURI_LOG_E(TAG, "File too large: %zu bytes", length);
return Type4TagErrorNotSupported;
}
static const uint8_t type_4_tag_iso_read_apdu[] = {
TYPE_4_TAG_ISO_READ_CMD,
};
while(length > 0) {
uint8_t chunk_len = MIN(length, chunk_max);
uint8_t offset_be[sizeof(offset)];
bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be);
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_iso_read_apdu, sizeof(type_4_tag_iso_read_apdu));
bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be));
bit_buffer_append_byte(instance->tx_buffer, chunk_len);
Type4TagError error =
type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer);
if(error != Type4TagErrorNone) {
return error;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) != chunk_len) {
FURI_LOG_E(
TAG,
"Wrong chunk len: %zu != %zu",
bit_buffer_get_size_bytes(instance->rx_buffer),
chunk_len);
return Type4TagErrorWrongFormat;
}
memcpy(buffer, bit_buffer_get_data(instance->rx_buffer), chunk_len);
buffer += chunk_len;
offset += chunk_len;
length -= chunk_len;
}
return Type4TagErrorNone;
}
static Type4TagError type_4_tag_poller_iso_write(
Type4TagPoller* instance,
uint16_t offset,
uint16_t length,
uint8_t* buffer) {
const uint8_t chunk_max = instance->data->is_tag_specific ?
MIN(instance->data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) :
TYPE_4_TAG_CHUNK_LEN;
if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) {
FURI_LOG_E(TAG, "File too large: %zu bytes", length);
return Type4TagErrorNotSupported;
}
static const uint8_t type_4_tag_iso_write_apdu[] = {
TYPE_4_TAG_ISO_WRITE_CMD,
};
while(length > 0) {
uint8_t chunk_len = MIN(length, chunk_max);
uint8_t offset_be[sizeof(offset)];
bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be);
bit_buffer_append_bytes(
instance->tx_buffer, type_4_tag_iso_write_apdu, sizeof(type_4_tag_iso_write_apdu));
bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be));
bit_buffer_append_byte(instance->tx_buffer, chunk_len);
bit_buffer_append_bytes(instance->tx_buffer, buffer, chunk_len);
Type4TagError error =
type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer);
if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardLocked;
if(error != Type4TagErrorNone) {
return error;
}
buffer += chunk_len;
offset += chunk_len;
length -= chunk_len;
}
return Type4TagErrorNone;
}
Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance) {
furi_check(instance);
Iso14443_4aPollerEvent iso14443_4a_event = {
.type = Iso14443_4aPollerEventTypeReady,
.data = NULL,
};
NfcGenericEvent event = {
.protocol = NfcProtocolIso14443_4a,
.instance = instance->iso14443_4a_poller,
.event_data = &iso14443_4a_event,
};
Type4TagPlatform platform = Type4TagPlatformUnknown;
NfcDevice* device = nfc_device_alloc();
do {
FURI_LOG_D(TAG, "Detect NTAG4xx");
Ntag4xxPoller* ntag4xx = ntag4xx_poller.alloc(instance->iso14443_4a_poller);
if(ntag4xx_poller.detect(event, ntag4xx)) {
platform = Type4TagPlatformNtag4xx;
nfc_device_set_data(device, NfcProtocolNtag4xx, ntag4xx_poller.get_data(ntag4xx));
}
ntag4xx_poller.free(ntag4xx);
if(platform != Type4TagPlatformUnknown) break;
FURI_LOG_D(TAG, "Detect DESFire");
MfDesfirePoller* mf_desfire = mf_desfire_poller.alloc(instance->iso14443_4a_poller);
mf_desfire_poller_set_command_mode(mf_desfire, NxpNativeCommandModeIsoWrapped);
if(mf_desfire_poller.detect(event, mf_desfire)) {
platform = Type4TagPlatformMfDesfire;
nfc_device_set_data(
device, NfcProtocolMfDesfire, mf_desfire_poller.get_data(mf_desfire));
}
mf_desfire_poller.free(mf_desfire);
if(platform != Type4TagPlatformUnknown) break;
} while(false);
Type4TagError error;
if(platform != Type4TagPlatformUnknown) {
furi_string_set(
instance->data->platform_name, nfc_device_get_name(device, NfcDeviceNameTypeFull));
error = Type4TagErrorNone;
} else {
furi_string_reset(instance->data->platform_name);
error = Type4TagErrorNotSupported;
}
instance->data->platform = platform;
nfc_device_free(device);
return error;
}
Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance) {
furi_check(instance);
FURI_LOG_D(TAG, "Select application");
return type_4_tag_poller_iso_select_name(
instance, type_4_tag_iso_df_name, sizeof(type_4_tag_iso_df_name));
}
Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance) {
furi_check(instance);
Type4TagError error;
do {
FURI_LOG_D(TAG, "Select CC");
error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(TAG, "Read CC len");
uint16_t cc_len;
uint8_t cc_len_be[sizeof(cc_len)];
error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_len_be), cc_len_be);
if(error != Type4TagErrorNone) break;
cc_len = bit_lib_bytes_to_num_be(cc_len_be, sizeof(cc_len_be));
FURI_LOG_D(TAG, "Read CC");
uint8_t cc_buf[cc_len];
error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_buf), cc_buf);
if(error != Type4TagErrorNone) break;
error = type_4_tag_cc_parse(instance->data, cc_buf, sizeof(cc_buf));
if(error != Type4TagErrorNone) break;
instance->data->is_tag_specific = true;
FURI_LOG_D(TAG, "Detected NDEF file ID %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");
error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(TAG, "Read NDEF len");
uint16_t ndef_len;
uint8_t ndef_len_be[sizeof(ndef_len)];
error = type_4_tag_poller_iso_read(instance, 0, sizeof(ndef_len_be), ndef_len_be);
if(error != Type4TagErrorNone) break;
ndef_len = bit_lib_bytes_to_num_be(ndef_len_be, sizeof(ndef_len_be));
if(ndef_len == 0) {
FURI_LOG_D(TAG, "NDEF file is empty");
break;
}
FURI_LOG_D(TAG, "Read NDEF");
simple_array_init(instance->data->ndef_data, ndef_len);
uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data);
error = type_4_tag_poller_iso_read(instance, sizeof(ndef_len), ndef_len, ndef_buf);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(
TAG, "Read %hu bytes from NDEF file %04X", ndef_len, instance->data->ndef_file_id);
} while(false);
return error;
}
Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance) {
Type4TagError error = Type4TagErrorNotSupported;
if(instance->data->platform == Type4TagPlatformMfDesfire) {
MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller);
mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped);
MfDesfireError mf_des_error;
do {
FURI_LOG_D(TAG, "Select DESFire PICC");
mf_des_error = mf_desfire_poller_select_application(mf_des, &mf_des_picc_app_id);
if(mf_des_error != MfDesfireErrorNone) {
error = Type4TagErrorProtocol;
break;
}
FURI_LOG_D(TAG, "Create DESFire T4T app");
mf_des_error = mf_desfire_poller_create_application(
mf_des,
&mf_des_t4t_app_id,
&mf_des_t4t_app_key_settings,
TYPE_4_TAG_ISO_DF_ID,
type_4_tag_iso_df_name,
sizeof(type_4_tag_iso_df_name));
if(mf_des_error != MfDesfireErrorNone) {
if(mf_des_error != MfDesfireErrorNotPresent &&
mf_des_error != MfDesfireErrorTimeout) {
error = Type4TagErrorCardLocked;
} else {
error = Type4TagErrorProtocol;
}
break;
}
error = Type4TagErrorNone;
} while(false);
mf_desfire_poller.free(mf_des);
}
return error;
}
Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance) {
Type4TagError error = Type4TagErrorNotSupported;
if(instance->data->platform == Type4TagPlatformMfDesfire) {
MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller);
mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped);
MfDesfireError mf_des_error;
do {
FURI_LOG_D(TAG, "Create DESFire CC");
mf_des_error = mf_desfire_poller_create_file(
mf_des, MF_DES_T4T_CC_FILE_ID, &mf_des_t4t_cc_file, TYPE_4_TAG_T4T_CC_EF_ID);
if(mf_des_error != MfDesfireErrorNone) {
if(mf_des_error != MfDesfireErrorNotPresent &&
mf_des_error != MfDesfireErrorTimeout) {
error = Type4TagErrorCardLocked;
} else {
error = Type4TagErrorProtocol;
}
break;
}
FURI_LOG_D(TAG, "Select CC");
error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(TAG, "Write DESFire CC");
instance->data->t4t_version.value = TYPE_4_TAG_T4T_CC_VNO;
instance->data->chunk_max_read = 0x3A;
instance->data->chunk_max_write = 0x34;
instance->data->ndef_file_id = TYPE_4_TAG_T4T_NDEF_EF_ID;
instance->data->ndef_max_len = TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE;
instance->data->ndef_read_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE;
instance->data->ndef_write_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE;
instance->data->is_tag_specific = true;
uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE];
type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf));
error = type_4_tag_poller_iso_write(instance, 0, sizeof(cc_buf), cc_buf);
if(error != Type4TagErrorNone) break;
error = Type4TagErrorNone;
} while(false);
mf_desfire_poller.free(mf_des);
}
return error;
}
Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance) {
Type4TagError error = Type4TagErrorNotSupported;
if(instance->data->platform == Type4TagPlatformMfDesfire) {
MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller);
mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped);
MfDesfireError mf_des_error;
do {
FURI_LOG_D(TAG, "Create DESFire NDEF");
MfDesfireFileSettings mf_des_t4t_ndef_file = mf_des_t4t_ndef_file_default;
mf_des_t4t_ndef_file.data.size = sizeof(uint16_t) + (instance->data->is_tag_specific ?
instance->data->ndef_max_len :
TYPE_4_TAG_DEFAULT_NDEF_SIZE);
mf_des_error = mf_desfire_poller_create_file(
mf_des, MF_DES_T4T_NDEF_FILE_ID, &mf_des_t4t_ndef_file, TYPE_4_TAG_T4T_NDEF_EF_ID);
if(mf_des_error != MfDesfireErrorNone) {
if(mf_des_error != MfDesfireErrorNotPresent &&
mf_des_error != MfDesfireErrorTimeout) {
error = Type4TagErrorCardLocked;
} else {
error = Type4TagErrorProtocol;
}
break;
}
error = Type4TagErrorNone;
} while(false);
mf_desfire_poller.free(mf_des);
}
return error;
}
Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance) {
furi_check(instance);
Type4TagError error;
do {
FURI_LOG_D(TAG, "Select NDEF");
error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(TAG, "Write NDEF len");
uint16_t ndef_len = simple_array_get_count(instance->data->ndef_data);
uint8_t ndef_len_be[sizeof(ndef_len)];
bit_lib_num_to_bytes_be(ndef_len, sizeof(ndef_len_be), ndef_len_be);
error = type_4_tag_poller_iso_write(instance, 0, sizeof(ndef_len_be), ndef_len_be);
if(error != Type4TagErrorNone) break;
if(ndef_len == 0) {
FURI_LOG_D(TAG, "NDEF file is empty");
break;
}
FURI_LOG_D(TAG, "Write NDEF");
uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data);
error = type_4_tag_poller_iso_write(instance, sizeof(ndef_len), ndef_len, ndef_buf);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(
TAG, "Wrote %hu bytes to NDEF file %04X", ndef_len, instance->data->ndef_file_id);
} while(false);
return error;
}
@@ -0,0 +1,61 @@
#pragma once
#include "type_4_tag_poller.h"
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
Type4TagPollerStateIdle,
Type4TagPollerStateRequestMode,
Type4TagPollerStateDetectPlatform,
Type4TagPollerStateSelectApplication,
Type4TagPollerStateReadCapabilityContainer,
Type4TagPollerStateReadNdefMessage,
Type4TagPollerStateCreateApplication,
Type4TagPollerStateCreateCapabilityContainer,
Type4TagPollerStateCreateNdefMessage,
Type4TagPollerStateWriteNdefMessage,
Type4TagPollerStateFailed,
Type4TagPollerStateSuccess,
Type4TagPollerStateNum,
} Type4TagPollerState;
struct Type4TagPoller {
Iso14443_4aPoller* iso14443_4a_poller;
Type4TagPollerState state;
Type4TagPollerMode mode;
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_poller_detect_platform(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);
Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance);
Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance);
Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance);
Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance);
#ifdef __cplusplus
}
#endif
+1
View File
@@ -2737,6 +2737,7 @@ Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*"
Function,+,submenu_free,void,Submenu*
Function,+,submenu_get_selected_item,uint32_t,Submenu*
Function,+,submenu_get_view,View*,Submenu*
Function,+,submenu_remove_item,void,"Submenu*, uint32_t"
Function,+,submenu_reset,void,Submenu*
Function,+,submenu_set_header,void,"Submenu*, const char*"
Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t"
1 entry status name type params
2737 Function + submenu_free void Submenu*
2738 Function + submenu_get_selected_item uint32_t Submenu*
2739 Function + submenu_get_view View* Submenu*
2740 Function + submenu_remove_item void Submenu*, uint32_t
2741 Function + submenu_reset void Submenu*
2742 Function + submenu_set_header void Submenu*, const char*
2743 Function + submenu_set_selected_item void Submenu*, uint32_t
+52 -1
View File
@@ -161,6 +161,9 @@ Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,,
Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,,
Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,,
Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,,
Header,+,lib/nfc/protocols/iso15693_3/iso15693_3.h,,
Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_listener.h,,
Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_poller.h,,
Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,,
Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,,
Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,,
@@ -173,11 +176,17 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,,
Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,,
Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,,
Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,,
Header,+,lib/nfc/protocols/ntag4xx/ntag4xx.h,,
Header,+,lib/nfc/protocols/ntag4xx/ntag4xx_poller.h,,
Header,+,lib/nfc/protocols/slix/slix.h,,
Header,+,lib/nfc/protocols/slix/slix_listener.h,,
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_listener.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,,
@@ -2191,6 +2200,9 @@ Function,+,iso14443_3a_get_device_name,const char*,"const Iso14443_3aData*, NfcD
Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData*
Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*"
Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*"
Function,+,iso14443_3a_listener_send_standard_frame,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*"
Function,+,iso14443_3a_listener_tx,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*"
Function,+,iso14443_3a_listener_tx_with_custom_parity,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*"
Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t"
Function,+,iso14443_3a_poller_activate,Iso14443_3aError,"Iso14443_3aPoller*, Iso14443_3aData*"
Function,+,iso14443_3a_poller_check_presence,Iso14443_3aError,Iso14443_3aPoller*
@@ -2237,6 +2249,7 @@ Function,+,iso14443_4a_get_fwt_fc_max,uint32_t,const Iso14443_4aData*
Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aData*, uint32_t*"
Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*"
Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*"
Function,+,iso14443_4a_listener_send_block,Iso14443_4aError,"Iso14443_4aListener*, const BitBuffer*"
Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t"
Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller*
Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*"
@@ -2281,6 +2294,13 @@ Function,+,iso15693_3_get_uid,const uint8_t*,"const Iso15693_3Data*, size_t*"
Function,+,iso15693_3_is_block_locked,_Bool,"const Iso15693_3Data*, uint8_t"
Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Data*"
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_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_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"
@@ -2709,6 +2729,8 @@ Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDe
Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*"
Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*"
Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t"
Function,+,mf_desfire_poller_create_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*, const MfDesfireKeySettings*, uint16_t, const uint8_t*, uint8_t"
Function,+,mf_desfire_poller_create_file,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, const MfDesfireFileSettings*, uint16_t"
Function,+,mf_desfire_poller_read_application,MfDesfireError,"MfDesfirePoller*, MfDesfireApplication*"
Function,+,mf_desfire_poller_read_application_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*"
Function,+,mf_desfire_poller_read_applications,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*"
@@ -2725,9 +2747,10 @@ Function,+,mf_desfire_poller_read_key_version,MfDesfireError,"MfDesfirePoller*,
Function,+,mf_desfire_poller_read_key_versions,MfDesfireError,"MfDesfirePoller*, SimpleArray*, uint32_t"
Function,+,mf_desfire_poller_read_version,MfDesfireError,"MfDesfirePoller*, MfDesfireVersion*"
Function,+,mf_desfire_poller_select_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*"
Function,+,mf_desfire_poller_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*"
Function,+,mf_desfire_poller_set_command_mode,void,"MfDesfirePoller*, NxpNativeCommandMode"
Function,+,mf_desfire_reset,void,MfDesfireData*
Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*"
Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*"
Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t"
Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*"
Function,+,mf_plus_alloc,MfPlusData*,
@@ -2981,6 +3004,19 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not
Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*"
Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*"
Function,-,nrand48,long,unsigned short[3]
Function,+,ntag4xx_alloc,Ntag4xxData*,
Function,+,ntag4xx_copy,void,"Ntag4xxData*, const Ntag4xxData*"
Function,+,ntag4xx_free,void,Ntag4xxData*
Function,+,ntag4xx_get_base_data,Iso14443_4aData*,const Ntag4xxData*
Function,+,ntag4xx_get_device_name,const char*,"const Ntag4xxData*, NfcDeviceNameType"
Function,+,ntag4xx_get_type_from_version,Ntag4xxType,const Ntag4xxVersion* const
Function,+,ntag4xx_get_uid,const uint8_t*,"const Ntag4xxData*, size_t*"
Function,+,ntag4xx_is_equal,_Bool,"const Ntag4xxData*, const Ntag4xxData*"
Function,+,ntag4xx_load,_Bool,"Ntag4xxData*, FlipperFormat*, uint32_t"
Function,+,ntag4xx_reset,void,Ntag4xxData*
Function,+,ntag4xx_save,_Bool,"const Ntag4xxData*, FlipperFormat*"
Function,+,ntag4xx_set_uid,_Bool,"Ntag4xxData*, const uint8_t*, size_t"
Function,+,ntag4xx_verify,_Bool,"Ntag4xxData*, const FuriString*"
Function,+,number_input_alloc,NumberInput*,
Function,+,number_input_free,void,NumberInput*
Function,+,number_input_get_view,View*,NumberInput*
@@ -3712,6 +3748,7 @@ Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*"
Function,+,submenu_free,void,Submenu*
Function,+,submenu_get_selected_item,uint32_t,Submenu*
Function,+,submenu_get_view,View*,Submenu*
Function,+,submenu_remove_item,void,"Submenu*, uint32_t"
Function,+,submenu_reset,void,Submenu*
Function,+,submenu_set_header,void,"Submenu*, const char*"
Function,+,submenu_set_orientation,void,"Submenu*, ViewOrientation"
@@ -3779,6 +3816,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*
@@ -4480,7 +4529,9 @@ Variable,-,nfc_device_mf_classic,const NfcDeviceBase,
Variable,-,nfc_device_mf_desfire,const NfcDeviceBase,
Variable,-,nfc_device_mf_plus,const NfcDeviceBase,
Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase,
Variable,-,nfc_device_ntag4xx,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,
1 entry status name type params
161 Header + lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h
162 Header + lib/nfc/protocols/iso14443_4b/iso14443_4b.h
163 Header + lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h
164 Header + lib/nfc/protocols/iso15693_3/iso15693_3.h
165 Header + lib/nfc/protocols/iso15693_3/iso15693_3_listener.h
166 Header + lib/nfc/protocols/iso15693_3/iso15693_3_poller.h
167 Header + lib/nfc/protocols/mf_classic/mf_classic.h
168 Header + lib/nfc/protocols/mf_classic/mf_classic_listener.h
169 Header + lib/nfc/protocols/mf_classic/mf_classic_poller.h
176 Header + lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h
177 Header + lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h
178 Header + lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h
179 Header + lib/nfc/protocols/ntag4xx/ntag4xx.h
180 Header + lib/nfc/protocols/ntag4xx/ntag4xx_poller.h
181 Header + lib/nfc/protocols/slix/slix.h
182 Header + lib/nfc/protocols/slix/slix_listener.h
183 Header + lib/nfc/protocols/slix/slix_poller.h
184 Header + lib/nfc/protocols/st25tb/st25tb.h
185 Header + lib/nfc/protocols/st25tb/st25tb_poller.h
186 Header + lib/nfc/protocols/st25tb/st25tb_poller_sync.h
187 Header + lib/nfc/protocols/type_4_tag/type_4_tag.h
188 Header + lib/nfc/protocols/type_4_tag/type_4_tag_listener.h
189 Header + lib/nfc/protocols/type_4_tag/type_4_tag_poller.h
190 Header + lib/one_wire/maxim_crc.h
191 Header + lib/one_wire/one_wire_host.h
192 Header + lib/one_wire/one_wire_slave.h
2200 Function + iso14443_3a_get_sak uint8_t const Iso14443_3aData*
2201 Function + iso14443_3a_get_uid const uint8_t* const Iso14443_3aData*, size_t*
2202 Function + iso14443_3a_is_equal _Bool const Iso14443_3aData*, const Iso14443_3aData*
2203 Function + iso14443_3a_listener_send_standard_frame Iso14443_3aError Iso14443_3aListener*, const BitBuffer*
2204 Function + iso14443_3a_listener_tx Iso14443_3aError Iso14443_3aListener*, const BitBuffer*
2205 Function + iso14443_3a_listener_tx_with_custom_parity Iso14443_3aError Iso14443_3aListener*, const BitBuffer*
2206 Function + iso14443_3a_load _Bool Iso14443_3aData*, FlipperFormat*, uint32_t
2207 Function + iso14443_3a_poller_activate Iso14443_3aError Iso14443_3aPoller*, Iso14443_3aData*
2208 Function + iso14443_3a_poller_check_presence Iso14443_3aError Iso14443_3aPoller*
2249 Function + iso14443_4a_get_historical_bytes const uint8_t* const Iso14443_4aData*, uint32_t*
2250 Function + iso14443_4a_get_uid const uint8_t* const Iso14443_4aData*, size_t*
2251 Function + iso14443_4a_is_equal _Bool const Iso14443_4aData*, const Iso14443_4aData*
2252 Function + iso14443_4a_listener_send_block Iso14443_4aError Iso14443_4aListener*, const BitBuffer*
2253 Function + iso14443_4a_load _Bool Iso14443_4aData*, FlipperFormat*, uint32_t
2254 Function + iso14443_4a_poller_halt Iso14443_4aError Iso14443_4aPoller*
2255 Function + iso14443_4a_poller_read_ats Iso14443_4aError Iso14443_4aPoller*, Iso14443_4aAtsData*
2294 Function + iso15693_3_is_block_locked _Bool const Iso15693_3Data*, uint8_t
2295 Function + iso15693_3_is_equal _Bool const Iso15693_3Data*, const Iso15693_3Data*
2296 Function + iso15693_3_load _Bool Iso15693_3Data*, FlipperFormat*, uint32_t
2297 Function + iso15693_3_poller_activate Iso15693_3Error Iso15693_3Poller*, Iso15693_3Data*
2298 Function + iso15693_3_poller_get_blocks_security Iso15693_3Error Iso15693_3Poller*, uint8_t*, uint16_t
2299 Function + iso15693_3_poller_get_system_info Iso15693_3Error Iso15693_3Poller*, Iso15693_3SystemInfo*
2300 Function + iso15693_3_poller_inventory Iso15693_3Error Iso15693_3Poller*, uint8_t*
2301 Function + iso15693_3_poller_read_block Iso15693_3Error Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t
2302 Function + iso15693_3_poller_read_blocks Iso15693_3Error Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t
2303 Function + iso15693_3_poller_send_frame Iso15693_3Error Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t
2304 Function + iso15693_3_reset void Iso15693_3Data*
2305 Function + iso15693_3_save _Bool const Iso15693_3Data*, FlipperFormat*
2306 Function + iso15693_3_set_uid _Bool Iso15693_3Data*, const uint8_t*, size_t
2729 Function + mf_desfire_get_uid const uint8_t* const MfDesfireData*, size_t*
2730 Function + mf_desfire_is_equal _Bool const MfDesfireData*, const MfDesfireData*
2731 Function + mf_desfire_load _Bool MfDesfireData*, FlipperFormat*, uint32_t
2732 Function + mf_desfire_poller_create_application MfDesfireError MfDesfirePoller*, const MfDesfireApplicationId*, const MfDesfireKeySettings*, uint16_t, const uint8_t*, uint8_t
2733 Function + mf_desfire_poller_create_file MfDesfireError MfDesfirePoller*, MfDesfireFileId, const MfDesfireFileSettings*, uint16_t
2734 Function + mf_desfire_poller_read_application MfDesfireError MfDesfirePoller*, MfDesfireApplication*
2735 Function + mf_desfire_poller_read_application_ids MfDesfireError MfDesfirePoller*, SimpleArray*
2736 Function + mf_desfire_poller_read_applications MfDesfireError MfDesfirePoller*, const SimpleArray*, SimpleArray*
2747 Function + mf_desfire_poller_read_key_versions MfDesfireError MfDesfirePoller*, SimpleArray*, uint32_t
2748 Function + mf_desfire_poller_read_version MfDesfireError MfDesfirePoller*, MfDesfireVersion*
2749 Function + mf_desfire_poller_select_application MfDesfireError MfDesfirePoller*, const MfDesfireApplicationId*
2750 Function + mf_desfire_poller_send_chunks MfDesfireError MfDesfirePoller*, const BitBuffer*, BitBuffer*
2751 Function + mf_desfire_poller_set_command_mode void MfDesfirePoller*, NxpNativeCommandMode
2752 Function + mf_desfire_reset void MfDesfireData*
2753 Function + mf_desfire_save _Bool const MfDesfireData*, FlipperFormat*
Function + mf_desfire_send_chunks MfDesfireError MfDesfirePoller*, const BitBuffer*, BitBuffer*
2754 Function + mf_desfire_set_uid _Bool MfDesfireData*, const uint8_t*, size_t
2755 Function + mf_desfire_verify _Bool MfDesfireData*, const FuriString*
2756 Function + mf_plus_alloc MfPlusData*
3004 Function + notification_message void NotificationApp*, const NotificationSequence*
3005 Function + notification_message_block void NotificationApp*, const NotificationSequence*
3006 Function - nrand48 long unsigned short[3]
3007 Function + ntag4xx_alloc Ntag4xxData*
3008 Function + ntag4xx_copy void Ntag4xxData*, const Ntag4xxData*
3009 Function + ntag4xx_free void Ntag4xxData*
3010 Function + ntag4xx_get_base_data Iso14443_4aData* const Ntag4xxData*
3011 Function + ntag4xx_get_device_name const char* const Ntag4xxData*, NfcDeviceNameType
3012 Function + ntag4xx_get_type_from_version Ntag4xxType const Ntag4xxVersion* const
3013 Function + ntag4xx_get_uid const uint8_t* const Ntag4xxData*, size_t*
3014 Function + ntag4xx_is_equal _Bool const Ntag4xxData*, const Ntag4xxData*
3015 Function + ntag4xx_load _Bool Ntag4xxData*, FlipperFormat*, uint32_t
3016 Function + ntag4xx_reset void Ntag4xxData*
3017 Function + ntag4xx_save _Bool const Ntag4xxData*, FlipperFormat*
3018 Function + ntag4xx_set_uid _Bool Ntag4xxData*, const uint8_t*, size_t
3019 Function + ntag4xx_verify _Bool Ntag4xxData*, const FuriString*
3020 Function + number_input_alloc NumberInput*
3021 Function + number_input_free void NumberInput*
3022 Function + number_input_get_view View* NumberInput*
3748 Function + submenu_free void Submenu*
3749 Function + submenu_get_selected_item uint32_t Submenu*
3750 Function + submenu_get_view View* Submenu*
3751 Function + submenu_remove_item void Submenu*, uint32_t
3752 Function + submenu_reset void Submenu*
3753 Function + submenu_set_header void Submenu*, const char*
3754 Function + submenu_set_orientation void Submenu*, ViewOrientation
3816 Function - trunc double double
3817 Function - truncf float float
3818 Function - truncl long double long double
3819 Function + type_4_tag_alloc Type4TagData*
3820 Function + type_4_tag_copy void Type4TagData*, const Type4TagData*
3821 Function + type_4_tag_free void Type4TagData*
3822 Function + type_4_tag_get_base_data Iso14443_4aData* const Type4TagData*
3823 Function + type_4_tag_get_device_name const char* const Type4TagData*, NfcDeviceNameType
3824 Function + type_4_tag_get_uid const uint8_t* const Type4TagData*, size_t*
3825 Function + type_4_tag_is_equal _Bool const Type4TagData*, const Type4TagData*
3826 Function + type_4_tag_load _Bool Type4TagData*, FlipperFormat*, uint32_t
3827 Function + type_4_tag_reset void Type4TagData*
3828 Function + type_4_tag_save _Bool const Type4TagData*, FlipperFormat*
3829 Function + type_4_tag_set_uid _Bool Type4TagData*, const uint8_t*, size_t
3830 Function + type_4_tag_verify _Bool Type4TagData*, const FuriString*
3831 Function + uint8_to_hex_chars void const uint8_t*, uint8_t*, int
3832 Function - ungetc int int, FILE*
3833 Function - unsetenv int const char*
4529 Variable - nfc_device_mf_desfire const NfcDeviceBase
4530 Variable - nfc_device_mf_plus const NfcDeviceBase
4531 Variable - nfc_device_mf_ultralight const NfcDeviceBase
4532 Variable - nfc_device_ntag4xx const NfcDeviceBase
4533 Variable - nfc_device_st25tb const NfcDeviceBase
4534 Variable - nfc_device_type_4_tag const NfcDeviceBase
4535 Variable + sequence_audiovisual_alert const NotificationSequence
4536 Variable + sequence_blink_blue_10 const NotificationSequence
4537 Variable + sequence_blink_blue_100 const NotificationSequence