From 85b6b2b8966552a3cd49bf29eae7b0da771dcfa8 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:54:08 +0400 Subject: [PATCH] NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks (#4254) * SimpleArray attached to FelicaData * tx rx done. response parsing done (in log) * dynamic vector as buffer. rendering begin * On screen render for directory tree * flags in render to indicate is_public_readable * beautify render flags * format * offload dynamic vector into individual files * saving. exposed dir tree writing for double use * save: additional formatting * save: clean up and some additional notes * load done * delete unnecessary debug log * Load: safer way to handle backward compatibility `parsed` being true is only contingent on whether the header (device type, UID, etc) are correctly read. The detailed data can be absent if saved from previous versions. Side effects: 1. The data format version number must not increment. 2. Newer sections of dumps must be appended in the end of the file. * format * handle block reading according to IC type Old version was aimed for FeliCa Lite dumping, which doesn't apply to FeliCa standard. Thus they need to be diverged in the poller run workflow. * read block content works. rendering begin * Render Refactor: dir & dump view from submenu * Render: show IC type name * IC parsing function cleanup * Revert "IC parsing function cleanup" This reverts commit ee3f7bf125b54b10d238b0aeb657ba15f27f93ba. * Load: Standard dump. Fully backward compatible * format * sync API version * format saved file * delete unused variable * clean ups * IC type addition * correction * beautify attribute parsing * correction * Lite save: delete extra line * correction: FeliCa link in Lite-S mode * format * Save: simplify printing * update IC type parsing * conform to api standard: const resp ptr to ptr also slightly faster and more readable block dump loop * disambiguate workflow type vs ic type It was too confusing to have the ic name string telling you one thing and ic_type enum saying the other. Might as well use better naming to indicate the use case for the two things * beautify on device render * reject dynamic_vector, embrace m-array * lint * use full variable name * partial fix: poller context's data proper init * edit unit test dump IC code and a small bug fix for the Lite auth workflow * unit test felica dump PMm correction * Fixes for static analysis warnings --------- Co-authored-by: hedger Co-authored-by: hedger --- .../resources/unit_tests/nfc/Felica.nfc | 2 +- .../helpers/protocol_support/felica/felica.c | 13 +- .../protocol_support/felica/felica_render.c | 111 +++- .../protocol_support/felica/felica_render.h | 9 +- .../main/nfc/scenes/nfc_scene_config.h | 2 + .../nfc/scenes/nfc_scene_felica_more_info.c | 151 +++++ lib/nfc/protocols/felica/felica.c | 622 ++++++++++++++++-- lib/nfc/protocols/felica/felica.h | 63 ++ lib/nfc/protocols/felica/felica_i.c | 22 + lib/nfc/protocols/felica/felica_i.h | 10 + lib/nfc/protocols/felica/felica_poller.c | 215 +++++- lib/nfc/protocols/felica/felica_poller_i.c | 43 ++ lib/nfc/protocols/felica/felica_poller_i.h | 13 +- lib/nfc/protocols/felica/felica_poller_sync.c | 15 +- lib/subghz/protocols/gangqi.c | 2 +- lib/subghz/protocols/hollarm.c | 2 +- lib/subghz/protocols/scher_khan.c | 9 +- targets/f7/api_symbols.csv | 4 + 18 files changed, 1219 insertions(+), 89 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_more_info.c create mode 100644 lib/nfc/protocols/felica/felica_i.c create mode 100644 lib/nfc/protocols/felica/felica_i.h diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc index 93ba4ba6c..4fb938537 100644 --- a/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc @@ -7,7 +7,7 @@ UID: 29 9F FA 53 AB 75 87 6E # FeliCa specific data Data format version: 1 Manufacture id: 29 9F FA 53 AB 75 87 6E -Manufacture parameter: 57 4E 10 2A 94 16 BC 8E +Manufacture parameter: 00 F1 00 00 00 01 43 00 Blocks total: 28 Blocks read: 28 Block 0: 00 00 DE AD BE AF 00 00 00 00 00 00 00 00 DE AD BE AF diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 561cd4d2e..0996ca7a1 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -39,17 +39,8 @@ static bool nfc_scene_info_on_event_felica(NfcApp* instance, SceneManagerEvent e } static void nfc_scene_more_info_on_enter_felica(NfcApp* instance) { - const NfcDevice* device = instance->nfc_device; - const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); - - FuriString* temp_str = furi_string_alloc(); - - nfc_render_felica_dump(data, temp_str); - - widget_add_text_scroll_element( - instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); - - furi_string_free(temp_str); + // Jump to advanced scene right away + scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaMoreInfo); } static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, void* context) { diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 6c57fb24b..1ca992bcd 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -4,9 +4,16 @@ void nfc_render_felica_blocks_count( const FelicaData* data, FuriString* str, bool render_auth_notification) { - furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); - if(render_auth_notification && data->blocks_read != data->blocks_total) { - furi_string_cat_printf(str, "\nAuth-protected blocks!"); + if(data->workflow_type == FelicaLite) { + furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); + + furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); + if(render_auth_notification && data->blocks_read != data->blocks_total) { + furi_string_cat_printf(str, "\nAuth-protected blocks!"); + } + } else if(data->workflow_type == FelicaStandard) { + furi_string_cat_printf( + str, "Public blocks Read: %lu", simple_array_get_count(data->public_blocks)); } } @@ -32,6 +39,11 @@ void nfc_render_felica_info( furi_string_cat_printf(str, "Tech: JIS X 6319-4,\nISO 18092 [NFC-F]\n"); } + FuriString* ic_type_str = furi_string_alloc(); + felica_get_ic_name(data, ic_type_str); + furi_string_cat_printf(str, "IC Type:\n%s\n", furi_string_get_cstr(ic_type_str)); + furi_string_free(ic_type_str); + nfc_render_felica_idm(data, format_type, str); if(format_type == NfcProtocolFormatTypeFull) { @@ -40,6 +52,14 @@ void nfc_render_felica_info( furi_string_cat_printf(str, "%02X ", data->pmm.data[i]); } } + + furi_string_cat_printf(str, "\n"); + furi_string_cat_printf( + str, + "Services found: %lu \nAreas found: %lu\n", + simple_array_get_count(data->services), + simple_array_get_count(data->areas)); + nfc_render_felica_blocks_count(data, str, true); } @@ -59,13 +79,18 @@ static void nfc_render_felica_block_name( static void nfc_render_felica_block_data(const FelicaBlock* block, FuriString* str) { furi_string_cat_printf(str, "\nSF1=%02X; SF2=%02X\n", block->SF1, block->SF2); - for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { - if((j != 0) && (j % 8 == 0)) furi_string_cat_printf(str, "\n"); - furi_string_cat_printf(str, "%02X ", block->data[j]); + for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i += 2) { + furi_string_cat_printf(str, "%02X%02X ", block->data[i], block->data[i + 1]); } furi_string_cat_printf(str, "\n"); } +static void nfc_render_felica_block_data_simple(const FelicaBlock* block, FuriString* str) { + for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i += 2) { + furi_string_cat_printf(str, "%02X%02X ", block->data[i], block->data[i + 1]); + } +} + static void nfc_render_felica_block( const FelicaBlock* block, FuriString* str, @@ -76,8 +101,13 @@ static void nfc_render_felica_block( nfc_render_felica_block_data(block, str); } -void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { +void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* str) { FuriString* name = furi_string_alloc(); + + furi_string_cat_printf(str, "\e#Blocks read:\n"); + + furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); + for(size_t i = 0; i < 14; i++) { furi_string_printf(name, "S_PAD%d", i); uint8_t suf_cnt = 18; @@ -105,3 +135,70 @@ void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } + +void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str) { + const size_t area_count = simple_array_get_count(data->areas); + const size_t service_count = simple_array_get_count(data->services); + + furi_string_cat_printf(str, "\e#Directory Tree:\n"); + + if(area_count == 0 || service_count == 0) { + furi_string_cat_printf(str, "No services or areas found.\n"); + } else { + furi_string_cat_printf( + str, "%zu areas found.\n%zu services found.\n\n", area_count, service_count); + furi_string_cat_printf( + str, "::: ... are readable services\n||| ... are locked services\n"); + } + felica_write_directory_tree(data, str); +} + +void nfc_more_info_render_felica_blocks( + const FelicaData* data, + FuriString* str, + const uint16_t service_code_key) { + furi_string_cat_printf(str, "\n"); + if(data->workflow_type == FelicaLite) { + furi_string_cat_printf(str, "Blocks: %u\n", data->blocks_total); + FuriString* name = furi_string_alloc(); + + for(size_t i = 0; i < 14; i++) { + furi_string_printf(name, "S_PAD%d", i); + uint8_t suf_cnt = 18; + if(i == 1) { + suf_cnt = 19; + } else if((i == 10) || (i == 12) || (i == 13)) { + suf_cnt = 16; + } + nfc_render_felica_block( + &data->data.fs.spad[i], str, furi_string_get_cstr(name), 20, suf_cnt); + } + furi_string_free(name); + nfc_render_felica_block(&data->data.fs.reg, str, "REG", 23, 23); + nfc_render_felica_block(&data->data.fs.rc, str, "RC", 25, 25); + nfc_render_felica_block(&data->data.fs.mac, str, "MAC", 23, 23); + nfc_render_felica_block(&data->data.fs.id, str, "ID", 25, 25); + nfc_render_felica_block(&data->data.fs.d_id, str, "D_ID", 22, 24); + nfc_render_felica_block(&data->data.fs.ser_c, str, "SER_C", 20, 21); + nfc_render_felica_block(&data->data.fs.sys_c, str, "SYS_C", 20, 21); + nfc_render_felica_block(&data->data.fs.ckv, str, "CKV", 23, 23); + nfc_render_felica_block(&data->data.fs.ck, str, "CK", 25, 25); + nfc_render_felica_block(&data->data.fs.mc, str, "MC", 25, 24); + nfc_render_felica_block(&data->data.fs.wcnt, str, "WCNT", 22, 20); + nfc_render_felica_block(&data->data.fs.mac_a, str, "MAC_A", 20, 20); + nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); + nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); + + } else if(data->workflow_type == FelicaStandard) { + uint32_t public_blocks_count = simple_array_get_count(data->public_blocks); + for(size_t i = 0; i < public_blocks_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + if(public_block->service_code != service_code_key) { + continue; // Skip blocks not matching the requested service code + } + furi_string_cat_printf(str, "-----Block 0x%02X-----\n", public_block->block_idx); + nfc_render_felica_block_data_simple(&public_block->block, str); + furi_string_cat_printf(str, "\n"); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 48bea4433..5c7c6d036 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -14,9 +14,16 @@ void nfc_render_felica_info( NfcProtocolFormatType format_type, FuriString* str); -void nfc_render_felica_dump(const FelicaData* data, FuriString* str); +void nfc_more_info_render_felica_lite_dump(const FelicaData* data, FuriString* str); void nfc_render_felica_idm( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_more_info_render_felica_dir(const FelicaData* data, FuriString* str); + +void nfc_more_info_render_felica_blocks( + const FelicaData* data, + FuriString* str, + const uint16_t service_code_key); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 399d59b92..1e3fd506f 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -74,4 +74,6 @@ ADD_SCENE(nfc, slix_key_input, SlixKeyInput) ADD_SCENE(nfc, slix_unlock, SlixUnlock) ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) +ADD_SCENE(nfc, felica_more_info, FelicaMoreInfo) + ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_more_info.c b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c new file mode 100644 index 000000000..7eee3d7b3 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_more_info.c @@ -0,0 +1,151 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/felica/felica_render.h" + +enum { + FelicaMoreInfoStateMenu, + FelicaMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index +}; + +enum SubmenuIndex { + SubmenuIndexDirectory, + SubmenuIndexDynamic, // dynamic indices start here +}; + +void nfc_scene_felica_more_info_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + submenu_add_item( + submenu, + "Directory", + SubmenuIndexDirectory, + nfc_protocol_support_common_submenu_callback, + nfc); + + FuriString* label = furi_string_alloc(); + + switch(data->workflow_type) { + case FelicaLite: + furi_string_printf(label, "All blocks"); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + break; + case FelicaStandard: + for(uint32_t i = 0; i < simple_array_get_count(data->services); ++i) { + const FelicaService* service = simple_array_cget(data->services, i); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 1; + if(!is_public) { + continue; + } + furi_string_printf(label, "Readable serv %04X", service->code); + submenu_add_item( + submenu, + furi_string_get_cstr(label), + i + SubmenuIndexDynamic, + nfc_protocol_support_common_submenu_callback, + nfc); + } + break; + default: + break; + } + + furi_string_free(label); + + if(state >= FelicaMoreInfoStateItem) { + submenu_set_selected_item( + nfc->submenu, state - FelicaMoreInfoStateItem + SubmenuIndexDynamic); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateMenu); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_felica_more_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneFelicaMoreInfo); + const FelicaData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolFelica); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexDirectory) { + FuriString* temp_str = furi_string_alloc(); + nfc_more_info_render_felica_dir(data, temp_str); + + widget_add_text_scroll_element( + nfc->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneFelicaMoreInfo, + FelicaMoreInfoStateItem + SubmenuIndexDirectory); + consumed = true; + } else { + const uint16_t service_ind = event.event - 1; // offset the three enums above + + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + + switch(data->workflow_type) { + case FelicaLite: + nfc_more_info_render_felica_lite_dump(data, nfc->text_box_store); + break; + case FelicaStandard: + const FelicaService* service = simple_array_cget(data->services, service_ind); + furi_string_cat_printf(nfc->text_box_store, "Service 0x%04X\n", service->code); + nfc_more_info_render_felica_blocks(data, nfc->text_box_store, service->code); + break; + default: + furi_string_set_str(nfc->text_box_store, "IC type not implemented yet"); + break; + } + text_box_set_font(nfc->text_box, TextBoxFontHex); + text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateItem + event.event); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state >= FelicaMoreInfoStateItem) { + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneFelicaMoreInfo, FelicaMoreInfoStateMenu); + } else { + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + // Return directly to the Info scene + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_felica_more_info_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + widget_reset(nfc->widget); + text_box_reset(nfc->text_box); + furi_string_reset(nfc->text_box_store); + submenu_reset(nfc->submenu); +} diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index a298f37a9..4e0ff7472 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -1,4 +1,5 @@ -#include "felica.h" +#include "felica_i.h" +#include #include @@ -11,7 +12,7 @@ #define FELICA_MANUFACTURE_ID "Manufacture id" #define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter" -static const uint32_t felica_data_format_version = 1; +static const uint32_t felica_data_format_version = 2; /** @brief This is used in felica_prepare_first_block to define which * type of block needs to be prepared. @@ -39,24 +40,71 @@ const NfcDeviceBase nfc_device_felica = { FelicaData* felica_alloc(void) { FelicaData* data = malloc(sizeof(FelicaData)); + furi_check(data); + + data->services = simple_array_alloc(&felica_service_array_cfg); + data->areas = simple_array_alloc(&felica_area_array_cfg); + data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg); + furi_check(data->services); + furi_check(data->areas); + furi_check(data->public_blocks); return data; } void felica_free(FelicaData* data) { furi_check(data); + + furi_check(data->services); + simple_array_free(data->services); + + furi_check(data->areas); + simple_array_free(data->areas); + + furi_check(data->public_blocks); + simple_array_free(data->public_blocks); + free(data); } void felica_reset(FelicaData* data) { furi_check(data); - memset(data, 0, sizeof(FelicaData)); + + if(data->services) { + simple_array_reset(data->services); + } + + if(data->areas) { + simple_array_reset(data->areas); + } + + if(data->public_blocks) { + simple_array_reset(data->public_blocks); + } + + data->blocks_read = 0; + data->blocks_total = 0; + data->workflow_type = FelicaUnknown; + memset(&data->idm, 0, sizeof(data->idm)); + memset(&data->pmm, 0, sizeof(data->pmm)); + memset(&data->data, 0, sizeof(data->data)); } void felica_copy(FelicaData* data, const FelicaData* other) { furi_check(data); furi_check(other); - *data = *other; + felica_reset(data); + + data->idm = other->idm; + data->pmm = other->pmm; + data->blocks_total = other->blocks_total; + data->blocks_read = other->blocks_read; + data->data = other->data; + data->workflow_type = other->workflow_type; + + simple_array_copy(data->services, other->services); + simple_array_copy(data->areas, other->areas); + simple_array_copy(data->public_blocks, other->public_blocks); } bool felica_verify(FelicaData* data, const FuriString* device_type) { @@ -70,43 +118,164 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { furi_check(data); bool parsed = false; + FuriString* str_key_buffer = furi_string_alloc(); + FuriString* str_data_buffer = furi_string_alloc(); + // Header do { if(version < NFC_UNIFIED_FORMAT_VERSION) break; uint32_t data_format_version = 0; if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1)) break; - if(data_format_version != felica_data_format_version) break; + + // V1 saving function always treated everything as Felica Lite + // So we load the blocks as if everything is Felica Lite + if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE)) break; if(!flipper_format_read_hex( ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; - parsed = true; - uint32_t blocks_total = 0; - uint32_t blocks_read = 0; - if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; - if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; - data->blocks_total = (uint8_t)blocks_total; - data->blocks_read = (uint8_t)blocks_read; - - FuriString* temp_str = furi_string_alloc(); - for(uint8_t i = 0; i < data->blocks_total; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - ff, - furi_string_get_cstr(temp_str), - (&data->data.dump[i * sizeof(FelicaBlock)]), - sizeof(FelicaBlock))) { - parsed = false; - break; - } + felica_get_workflow_type(data); + if(data_format_version == 1) { + data->workflow_type = FelicaLite; } - furi_string_free(temp_str); + parsed = true; } while(false); + if(!parsed) { + furi_string_free(str_key_buffer); + furi_string_free(str_data_buffer); + return false; + } + + switch(data->workflow_type) { + case FelicaLite: + // Blocks data + do { + uint32_t blocks_total = 0; + uint32_t blocks_read = 0; + if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; + data->blocks_total = (uint8_t)blocks_total; + data->blocks_read = (uint8_t)blocks_read; + + for(uint8_t i = 0; i < data->blocks_total; i++) { + furi_string_printf(str_data_buffer, "Block %d", i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(str_data_buffer), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + break; + } + } + } while(false); + break; + case FelicaStandard: + // Areas + do { + uint32_t area_count = 0; + if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break; + simple_array_init(data->areas, area_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < area_count; i++) { + furi_string_printf(str_key_buffer, "Area %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaArea* area = simple_array_get(data->areas, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Code %04hX | Services #%03hX-#%03hX |", + &area->code, + &area->first_idx, + &area->last_idx) != 3) { + break; + } + } + } while(false); + + // Services + do { + uint32_t service_count = 0; + if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break; + simple_array_init(data->services, service_count); + + furi_string_reset(str_key_buffer); + furi_string_reset(str_data_buffer); + for(uint16_t i = 0; i < service_count; i++) { + furi_string_printf(str_key_buffer, "Service %03X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + FelicaService* service = simple_array_get(data->services, i); + + // all unread in the beginning. reserved for future block load + if(!sscanf( + furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) { + break; + } + service->attr = service->code & 0x3F; + } + } while(false); + + // Public blocks + do { + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + uint32_t public_block_count = 0; + if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + simple_array_init(data->public_blocks, public_block_count); + for(uint16_t i = 0; i < public_block_count; i++) { + furi_string_printf(str_key_buffer, "Block %04X", i); + if(!flipper_format_read_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) { + break; + } + + FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + if(sscanf( + furi_string_get_cstr(str_data_buffer), + "| Service code %04hX | Block index %02hhX |", + &public_block->service_code, + &public_block->block_idx) != 2) { + break; + } + + size_t needle = furi_string_search_str(str_data_buffer, "Data: "); + if(needle == FURI_STRING_FAILURE) { + break; + } + needle += 6; // length of "Data: " = 6 + furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE); + furi_string_replace_all(str_data_buffer, " ", ""); + if(!hex_chars_to_uint8( + furi_string_get_cstr(str_data_buffer), public_block->block.data)) { + break; + } + + furi_string_reset(str_data_buffer); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); + } + } + } while(false); + break; + default: + break; + } + + furi_string_free(str_key_buffer); + furi_string_free(str_data_buffer); + return parsed; } @@ -114,8 +283,10 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { furi_check(data); bool saved = false; - + FuriString* str_data_buffer = furi_string_alloc(); + FuriString* str_key_buffer = furi_string_alloc(); do { + // Header if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break; if(!flipper_format_write_uint32( ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1)) @@ -126,27 +297,134 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; - uint32_t blocks_total = data->blocks_total; - uint32_t blocks_read = data->blocks_read; - if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; - if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; - saved = true; - FuriString* temp_str = furi_string_alloc(); - for(uint8_t i = 0; i < blocks_total; i++) { - furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_write_hex( - ff, - furi_string_get_cstr(temp_str), - (&data->data.dump[i * sizeof(FelicaBlock)]), - sizeof(FelicaBlock))) { - saved = false; - break; - } - } - furi_string_free(temp_str); + + felica_get_ic_name(data, str_data_buffer); + furi_string_replace_all(str_data_buffer, "\n", " "); + if(!flipper_format_write_string(ff, "IC Type", str_data_buffer)) break; + if(!flipper_format_write_empty_line(ff)) break; } while(false); + switch(data->workflow_type) { + case FelicaLite: + if(!flipper_format_write_comment_cstr(ff, "Felica Lite specific data")) break; + // Blocks count + do { + uint32_t blocks_total = data->blocks_total; + uint32_t blocks_read = data->blocks_read; + if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; + + // Blocks data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint8_t i = 0; i < blocks_total; i++) { + furi_string_printf(str_key_buffer, "Block %d", i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(str_key_buffer), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + saved = false; + break; + } + } + } while(false); + break; + + case FelicaStandard: + if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break; + + do { + uint32_t area_count = simple_array_get_count(data->areas); + uint32_t service_count = simple_array_get_count(data->services); + // Note: The theoretical max area/service count is 2^10 + // So uint16_t is already enough for practical usage + // The following key index print will use %03X because 12 bits are enough to cover 0-1023 + + // Area count + if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break; + + // Area data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < area_count; i++) { + FelicaArea* area = simple_array_get(data->areas, i); + furi_string_printf(str_key_buffer, "Area %03X", i); + furi_string_printf( + str_data_buffer, + "| Code %04X | Services #%03X-#%03X |", + area->code, + area->first_idx, + area->last_idx); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Service count + if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break; + + // Service data + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < service_count; i++) { + FelicaService* service = simple_array_get(data->services, i); + furi_string_printf(str_key_buffer, "Service %03X", i); + furi_string_printf( + str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr); + felica_service_get_attribute_string(service, str_data_buffer); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + if(!flipper_format_write_empty_line(ff)) break; + + // Directory tree + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + furi_string_printf( + str_data_buffer, "\n::: ... are public services\n||| ... are private services"); + felica_write_directory_tree(data, str_data_buffer); + furi_string_replace_all(str_data_buffer, ":", "+"); + // We use a clearer marker in saved text files + if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break; + } while(false); + + // Public blocks + do { + uint32_t public_block_count = simple_array_get_count(data->public_blocks); + if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1)) + break; + furi_string_reset(str_data_buffer); + furi_string_reset(str_key_buffer); + for(uint16_t i = 0; i < public_block_count; i++) { + FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i); + furi_string_printf(str_key_buffer, "Block %04X", i); + furi_string_printf( + str_data_buffer, + "| Service code %04X | Block index %02X | Data: ", + public_block->service_code, + public_block->block_idx); + for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]); + } + furi_string_cat_printf(str_data_buffer, "|"); + if(!flipper_format_write_string( + ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) + break; + } + } while(false); + break; + default: + break; + } + + // Clean up + furi_string_free(str_data_buffer); + furi_string_free(str_key_buffer); + return saved; } @@ -154,7 +432,13 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) { furi_check(data); furi_check(other); - return memcmp(data, other, sizeof(FelicaData)) == 0; + return memcmp(data->idm.data, other->idm.data, sizeof(FelicaIDm)) == 0 && + memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 && + data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read && + memcmp(&data->data, &other->data, sizeof(data->data)) == 0 && + simple_array_is_equal(data->services, other->services) && + simple_array_is_equal(data->areas, other->areas) && + simple_array_is_equal(data->public_blocks, other->public_blocks); } const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) { @@ -355,3 +639,251 @@ void felica_calculate_mac_write( memcpy(session_swapped + 8, session_key, 8); felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); } + +void felica_write_directory_tree(const FelicaData* data, FuriString* str) { + furi_check(data); + furi_check(str); + + furi_string_cat_str(str, "\n"); + + uint16_t area_last_stack[8]; + uint8_t depth = 0; + + size_t area_iter = 0; + const size_t area_count = simple_array_get_count(data->areas); + const size_t service_count = simple_array_get_count(data->services); + + for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) { + while(area_iter < area_count) { + const FelicaArea* next_area = simple_array_get(data->areas, area_iter); + if(next_area->first_idx != svc_idx) break; + + for(uint8_t i = 0; i < depth - 1; ++i) + furi_string_cat_printf(str, "| "); + furi_string_cat_printf(str, depth ? "|" : ""); + furi_string_cat_printf(str, "- AREA_%04X/\n", next_area->code >> 6); + + area_last_stack[depth++] = next_area->last_idx; + area_iter++; + } + + const FelicaService* service = simple_array_get(data->services, svc_idx); + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; + + for(uint8_t i = 0; i < depth - 1; ++i) + furi_string_cat_printf(str, is_public ? ": " : "| "); + furi_string_cat_printf(str, is_public ? ":" : "|"); + furi_string_cat_printf(str, "- serv_%04X\n", service->code); + + if(depth && svc_idx >= area_last_stack[depth - 1]) depth--; + } +} + +void felica_get_workflow_type(FelicaData* data) { + // Reference: Proxmark3 repo + uint8_t rom_type = data->pmm.data[0]; + uint8_t workflow_type = data->pmm.data[1]; + if(workflow_type <= 0x48) { + // More liberal check because most of these should be treated as FeliCa Standard, regardless of mobile or not. + data->workflow_type = FelicaStandard; + } else { + switch(workflow_type) { + case 0xA2: + data->workflow_type = FelicaStandard; + break; + case 0xF0: + case 0xF1: + case 0xF2: // 0xF2 => FeliCa Link RC-S967 in Lite-S Mode or Lite-S HT Mode + data->workflow_type = FelicaLite; + break; + case 0xE1: // Felica Link + case 0xE0: // Felica Plug + data->workflow_type = FelicaUnknown; + break; + case 0xFF: + if(rom_type == 0xFF) { + data->workflow_type = FelicaUnknown; // Felica Link + } + break; + default: + data->workflow_type = FelicaUnknown; + break; + } + } +} + +void felica_get_ic_name(const FelicaData* data, FuriString* ic_name) { + // Reference: Proxmark3 repo + uint8_t rom_type = data->pmm.data[0]; + uint8_t ic_type = data->pmm.data[1]; + + switch(ic_type) { + // FeliCa Standard Products: + // odd findings + case 0x00: + furi_string_set_str(ic_name, "FeliCa Standard RC-S830"); + break; + case 0x01: + furi_string_set_str(ic_name, "FeliCa Standard RC-S915"); + break; + case 0x02: + furi_string_set_str(ic_name, "FeliCa Standard RC-S919"); + break; + case 0x06: + case 0x07: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V1.0"); + break; + case 0x08: + furi_string_set_str(ic_name, "FeliCa Standard RC-S952"); + break; + case 0x09: + furi_string_set_str(ic_name, "FeliCa Standard RC-S953"); + break; + case 0x0B: + furi_string_set_str(ic_name, "FeliCa Standard RC-S9X4,\nJapan Transit IC"); + break; + case 0x0C: + furi_string_set_str(ic_name, "FeliCa Standard RC-S954"); + break; + case 0x0D: + furi_string_set_str(ic_name, "FeliCa Standard RC-S960"); + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V2.0"); + break; + case 0x14: + case 0x15: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V3.0"); + break; + case 0x16: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nJapan Transit IC"); + break; + case 0x17: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.0"); + break; + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.1"); + break; + case 0x20: + furi_string_set_str(ic_name, "FeliCa Standard RC-S962"); + // RC-S962 has been extensively found in Japan Transit ICs, despite model number not ending in 4 + break; + case 0x31: + furi_string_set_str(ic_name, "FeliCa Standard RC-S104,\nJapan Transit IC"); + break; + case 0x32: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/1"); + break; + case 0x33: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/2"); + break; + case 0x34: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/1"); + break; + case 0x35: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/2"); + break; + case 0x36: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA04/1,\nJapan Transit IC"); + break; + case 0x3E: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA08/1"); + break; + case 0x43: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1"); + break; + case 0x44: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/1"); + break; + case 0x45: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/2"); + break; + case 0x46: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2"); + break; + case 0x47: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1x1"); + break; + case 0x48: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2x1"); + break; + case 0xA2: + furi_string_set_str(ic_name, "FeliCa Standard RC-SA14"); + break; + // NFC Dynamic Tag (FeliCa Plug) Products: + case 0xE0: + furi_string_set_str(ic_name, "FeliCa Plug RC-S926,\nNFC Dynamic Tag"); + break; + case 0xE1: + furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nPlug Mode"); + break; + case 0xF0: + furi_string_set_str(ic_name, "FeliCa Lite RC-S965"); + break; + case 0xF1: + furi_string_set_str(ic_name, "FeliCa Lite-S RC-S966"); + break; + case 0xF2: + furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nLite-S Mode or Lite-S HT Mode"); + break; + case 0xFF: + if(rom_type == 0xFF) { // from FeliCa Link User's Manual + furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nNFC-DEP Mode"); + } + break; + default: + furi_string_printf( + ic_name, + "Unknown IC %02X ROM %02X:\nPlease submit an issue on\nGitHub and help us identify.", + ic_type, + rom_type); + break; + } +} + +void felica_service_get_attribute_string(const FelicaService* service, FuriString* str) { + furi_check(service); + furi_check(str); + + bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0; + furi_string_cat_str(str, is_public ? "| Public " : "| Private "); + + bool is_purse = (service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE) != 0; + // Subfield bitwise attributes are applicable depending on is PURSE or not + + if(is_purse) { + furi_string_cat_str(str, "| Purse |"); + switch((service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD) >> 1) { + case 0: + furi_string_cat_str(str, " Direct |"); + break; + case 1: + furi_string_cat_str(str, " Cashback |"); + break; + case 2: + furi_string_cat_str(str, " Decrement |"); + break; + case 3: + furi_string_cat_str(str, " Read Only |"); + break; + default: + furi_string_cat_str(str, " Unknown |"); + break; + } + } else { + bool is_random = (service->attr & FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS) != 0; + furi_string_cat_str(str, is_random ? "| Random |" : "| Cyclic |"); + bool is_readonly = (service->attr & FELICA_SERVICE_ATTRIBUTE_READ_ONLY) != 0; + furi_string_cat_str(str, is_readonly ? " Read Only |" : " Read/Write |"); + } +} diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index 1fcaffc53..5a5a75215 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -34,6 +35,8 @@ extern "C" { #define FELICA_BLOCK_INDEX_STATE (0x92U) #define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U) +#define FELICA_STANDARD_MAX_BLOCK_COUNT (0xFFU) + #define FELICA_GUARD_TIME_US (20000U) #define FELICA_FDT_POLL_FC (10000U) #define FELICA_POLL_POLL_MIN_US (1280U) @@ -47,6 +50,16 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) +#define FELICA_CMD_LIST_SERVICE_CODE 0x0A +#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B + +#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001) +#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010) +#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000) +#define FELICA_SERVICE_ATTRIBUTE_CYCLIC (0b001100) +#define FELICA_SERVICE_ATTRIBUTE_PURSE (0b010000) +#define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD (0b000110) + /** @brief Type of possible Felica errors */ typedef enum { FelicaErrorNone, @@ -61,6 +74,12 @@ typedef enum { FelicaErrorFeatureUnsupported, } FelicaError; +typedef enum { + FelicaUnknown, + FelicaStandard, + FelicaLite, +} FelicaWorkflowType; + typedef struct { uint8_t data[FELICA_DATA_BLOCK_SIZE]; } FelicaBlockData; @@ -146,6 +165,23 @@ typedef union { uint8_t dump[sizeof(FelicaFileSystem)]; } FelicaFSUnion; +typedef struct { + uint16_t code; + uint8_t attr; +} FelicaService; + +typedef struct { + uint16_t code; + uint16_t first_idx; + uint16_t last_idx; +} FelicaArea; + +typedef struct { + FelicaBlock block; + uint16_t service_code; + uint8_t block_idx; +} FelicaPublicBlock; + /** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; @@ -153,6 +189,11 @@ typedef struct { uint8_t blocks_total; uint8_t blocks_read; FelicaFSUnion data; + + SimpleArray* services; + SimpleArray* areas; + SimpleArray* public_blocks; + FelicaWorkflowType workflow_type; } FelicaData; typedef struct FURI_PACKED { @@ -171,6 +212,14 @@ typedef struct { uint8_t SF2; } FelicaCommandResponseHeader; +#pragma pack(push, 1) +typedef struct { + uint8_t length; + uint8_t command; + FelicaIDm idm; +} FelicaCommandHeaderRaw; +#pragma pack(pop) + typedef struct { uint8_t service_code : 4; uint8_t access_mode : 3; @@ -194,6 +243,11 @@ typedef struct { uint8_t data[]; } FelicaListenerReadCommandResponse; +typedef struct { + FelicaCommandHeaderRaw header; + uint8_t data[]; +} FelicaListServiceCommandResponse; + typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse; typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse; @@ -254,6 +308,15 @@ void felica_calculate_mac_write( const uint8_t* wcnt, const uint8_t* data, uint8_t* mac); + +void felica_write_directory_tree(const FelicaData* data, FuriString* str); + +void felica_get_workflow_type(FelicaData* data); + +void felica_get_ic_name(const FelicaData* data, FuriString* ic_name); + +void felica_service_get_attribute_string(const FelicaService* service, FuriString* str); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_i.c b/lib/nfc/protocols/felica/felica_i.c new file mode 100644 index 000000000..e265ea862 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_i.c @@ -0,0 +1,22 @@ +#include "felica_i.h" + +const SimpleArrayConfig felica_service_array_cfg = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(FelicaService), +}; + +const SimpleArrayConfig felica_area_array_cfg = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(FelicaArea), +}; + +const SimpleArrayConfig felica_public_block_array_cfg = { + .init = NULL, + .copy = NULL, + .reset = NULL, + .type_size = sizeof(FelicaPublicBlock), +}; diff --git a/lib/nfc/protocols/felica/felica_i.h b/lib/nfc/protocols/felica/felica_i.h new file mode 100644 index 000000000..a708aa729 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_i.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include +#include "felica.h" + +#include + +extern const SimpleArrayConfig felica_service_array_cfg; +extern const SimpleArrayConfig felica_area_array_cfg; +extern const SimpleArrayConfig felica_public_block_array_cfg; diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index cf00cbc09..a6964c685 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -1,4 +1,6 @@ #include "felica_poller_i.h" +#include +#include #include @@ -7,6 +9,10 @@ #define TAG "FelicaPoller" +ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658 +ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658 + typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); const FelicaData* felica_poller_get_data(FelicaPoller* instance) { @@ -79,15 +85,30 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { FelicaError error = felica_poller_activate(instance, instance->data); if(error == FelicaErrorNone) { furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE); + felica_get_workflow_type(instance->data); instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext; instance->felica_event_data.auth_context = &instance->auth.context; instance->callback(instance->general_event, instance->context); + switch(instance->data->workflow_type) { + case FelicaStandard: + instance->state = FelicaPollerStateTraverseStandardSystem; + break; + case FelicaLite: + instance->state = FelicaPollerStateReadLiteBlocks; + break; + default: + // Unimplemented + instance->state = FelicaPollerStateReadSuccess; + break; + } + bool skip_auth = instance->auth.context.skip_auth; - instance->state = skip_auth ? FelicaPollerStateReadBlocks : - FelicaPollerStateAuthenticateInternal; + if(!skip_auth) { + instance->state = FelicaPollerStateAuthenticateInternal; + } } else if(error != FelicaErrorTimeout) { instance->felica_event.type = FelicaPollerEventTypeError; instance->felica_event_data.error = error; @@ -105,7 +126,18 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { instance->data->data.fs.rc.data, instance->auth.session_key.data); - instance->state = FelicaPollerStateReadBlocks; + switch(instance->data->workflow_type) { + case FelicaStandard: + instance->state = FelicaPollerStateTraverseStandardSystem; + break; + case FelicaLite: + instance->state = FelicaPollerStateReadLiteBlocks; + break; + default: + // Unimplemented + instance->state = FelicaPollerStateReadSuccess; + break; + } uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0}; FelicaPollerWriteCommandResponse* tx_resp; @@ -145,7 +177,6 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { FURI_LOG_D(TAG, "Auth External"); - instance->state = FelicaPollerStateReadBlocks; uint8_t blocks[2]; instance->data->data.fs.state.data[0] = 1; @@ -183,12 +214,177 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE); instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0]; } while(false); - instance->state = FelicaPollerStateReadBlocks; + + switch(instance->data->workflow_type) { + case FelicaStandard: + instance->state = FelicaPollerStateTraverseStandardSystem; + break; + case FelicaLite: + instance->state = FelicaPollerStateReadLiteBlocks; + break; + default: + // Unimplemented + instance->state = FelicaPollerStateReadSuccess; + break; + } + return NfcCommandContinue; } -NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { - FURI_LOG_D(TAG, "Read Blocks"); +NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Traverse Standard System"); + + FelicaListServiceCommandResponse* response; + + felica_service_array_t service_buffer; + felica_service_array_init(service_buffer); + felica_area_array_t area_buffer; + felica_area_array_init(area_buffer); + + for(uint16_t cursor = 0; cursor < 0xFFFF; cursor++) { + FelicaError error = felica_poller_list_service_by_cursor(instance, cursor, &response); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "Error %d at cursor %04X", error, cursor); + break; + } + + uint8_t len = response->header.length; + const uint8_t* list_service_payload = response->data; + uint16_t code_begin = (uint16_t)(list_service_payload[0] | (list_service_payload[1] << 8)); + + if(len != 0x0C && len != 0x0E) { + FURI_LOG_E(TAG, "Bad command resp length 0x%02X at cursor 0x%04X", len, cursor); + break; + } + + if(code_begin == 0xFFFF) { + FURI_LOG_D(TAG, "Traverse complete"); + break; + } + + if(len == 0x0E) { + FelicaArea* area = felica_area_array_push_raw(area_buffer); + memset(area, 0, sizeof *area); + area->code = code_begin; + area->first_idx = (uint16_t)felica_service_array_size(service_buffer); + area->last_idx = 0; + } else { + FelicaService* service = felica_service_array_push_raw(service_buffer); + memset(service, 0, sizeof *service); + service->code = code_begin; + service->attr = (uint8_t)(code_begin & 0x3F); + + if(felica_area_array_size(area_buffer)) { + FelicaArea* current_area = felica_area_array_back(area_buffer); + current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1); + } + } + } + + const size_t service_num = felica_service_array_size(service_buffer); + const size_t area_num = felica_area_array_size(area_buffer); + + if(service_num) { + simple_array_init(instance->data->services, (uint32_t)service_num); + memcpy( + simple_array_get(instance->data->services, 0), + service_buffer->ptr, + service_num * sizeof(FelicaService)); + } else { + simple_array_reset(instance->data->services); + } + + if(area_num) { + simple_array_init(instance->data->areas, (uint32_t)area_num); + memcpy( + simple_array_get(instance->data->areas, 0), + area_buffer->ptr, + area_num * sizeof(FelicaArea)); + } else { + simple_array_reset(instance->data->areas); + } + + FURI_LOG_I( + TAG, + "Services found: %lu, Areas found: %lu", + simple_array_get_count(instance->data->services), + simple_array_get_count(instance->data->areas)); + + felica_service_array_clear(service_buffer); + felica_area_array_clear(area_buffer); + + instance->state = FelicaPollerStateReadStandardBlocks; + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Standard Blocks"); + + const uint32_t service_count = simple_array_get_count(instance->data->services); + + felica_public_block_array_t public_block_buffer; + felica_public_block_array_init(public_block_buffer); + + instance->state = FelicaPollerStateReadSuccess; + bool have_read_anything = false; + + for(uint32_t i = 0; i < service_count; i++) { + const FelicaService* service = simple_array_get(instance->data->services, i); + + if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue; + + uint8_t block_count = 1; + uint8_t block_list[1] = {0}; + FelicaError error = FelicaErrorNone; + FelicaPollerReadCommandResponse* response; + do { + error = felica_poller_read_blocks( + instance, block_count, block_list, service->code, &response); + + if(error != FelicaErrorNone) { + break; + } + + if(response->SF1 == 0 && response->SF2 == 0) { + FelicaPublicBlock* public_block = + felica_public_block_array_push_raw(public_block_buffer); + memset(public_block, 0, sizeof *public_block); + memcpy(public_block->block.data, response->data, FELICA_DATA_BLOCK_SIZE); + + public_block->service_code = service->code; + public_block->block_idx = block_list[0]; + + have_read_anything = true; + block_list[0]++; + } else { + break; // No more blocks to read in this service, ok to continue for loop + } + } while(block_list[0] < FELICA_STANDARD_MAX_BLOCK_COUNT); + + if(error != FelicaErrorNone) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + break; + } + } + + if(have_read_anything) { + const size_t n = felica_public_block_array_size(public_block_buffer); + simple_array_init(instance->data->public_blocks, (uint32_t)n); + memcpy( + simple_array_get(instance->data->public_blocks, 0), + public_block_buffer->ptr, + n * sizeof(FelicaPublicBlock)); + } + + felica_public_block_array_clear(public_block_buffer); + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Lite Blocks"); uint8_t block_count = 1; uint8_t block_list[4] = {0, 0, 0, 0}; @@ -266,7 +462,10 @@ static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] [FelicaPollerStateActivated] = felica_poller_state_handler_activate, [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, - [FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks, + [FelicaPollerStateTraverseStandardSystem] = + felica_poller_state_handler_traverse_standard_system, + [FelicaPollerStateReadStandardBlocks] = felica_poller_state_handler_read_standard_blocks, + [FelicaPollerStateReadLiteBlocks] = felica_poller_state_handler_read_lite_blocks, [FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success, [FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed, }; diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 49112debd..4ab9a8e4c 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -93,6 +93,7 @@ FelicaError felica_poller_polling( return error; } +// This is in fact a buffer preparer for a specified service. It should be have the _ex suffix. The prepare_tx_buffer_raw should have this name. static void felica_poller_prepare_tx_buffer( const FelicaPoller* instance, const uint8_t command, @@ -221,3 +222,45 @@ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { return ret; } + +static void felica_poller_prepare_tx_buffer_raw( + const FelicaPoller* instance, + const uint8_t command, + const uint8_t* data, + const uint8_t data_length) { + FelicaCommandHeaderRaw cmd = {.length = 0x00, .command = command, .idm = instance->data->idm}; + + cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw)); + bit_buffer_append_bytes(instance->tx_buffer, data, data_length); +} + +FelicaError felica_poller_list_service_by_cursor( + FelicaPoller* instance, + uint16_t cursor, + FelicaListServiceCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(response_ptr); + + const uint8_t data[2] = {(uint8_t)(cursor & 0xFF), (uint8_t)((cursor >> 8) & 0xFF)}; + + felica_poller_prepare_tx_buffer_raw( + instance, FELICA_CMD_LIST_SERVICE_CODE, data, sizeof(data)); + + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "List service by cursor failed with error: %d", error); + return error; + } + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len < sizeof(FelicaCommandHeaderRaw) + 2) return FelicaErrorProtocol; + + // error is known to be FelicaErrorNone here + *response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + return error; +} diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index df4990a4e..98948db86 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -19,7 +19,9 @@ typedef enum { FelicaPollerStateActivated, FelicaPollerStateAuthenticateInternal, FelicaPollerStateAuthenticateExternal, - FelicaPollerStateReadBlocks, + FelicaPollerStateTraverseStandardSystem, + FelicaPollerStateReadStandardBlocks, + FelicaPollerStateReadLiteBlocks, FelicaPollerStateReadSuccess, FelicaPollerStateReadFailed, @@ -55,6 +57,10 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; +typedef union { + FelicaData* data; +} FelicaPollerContextData; + const FelicaData* felica_poller_get_data(FelicaPoller* instance); /** @@ -105,6 +111,11 @@ FelicaError felica_poller_frame_exchange( BitBuffer* rx_buffer, uint32_t fwt); +FelicaError felica_poller_list_service_by_cursor( + FelicaPoller* instance, + uint16_t cursor, + FelicaListServiceCommandResponse** response_ptr); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_sync.c b/lib/nfc/protocols/felica/felica_poller_sync.c index ec73227f5..707a0cbf3 100644 --- a/lib/nfc/protocols/felica/felica_poller_sync.c +++ b/lib/nfc/protocols/felica/felica_poller_sync.c @@ -11,8 +11,8 @@ typedef struct { FelicaAuthenticationContext auth_ctx; FuriThreadId thread_id; FelicaError error; - FelicaData data; -} Felica_PollerContext; + FelicaPollerContextData data; +} FelicaPollerContext; NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) { furi_assert(context); @@ -20,14 +20,14 @@ NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) { furi_assert(event.instance); furi_assert(event.protocol == NfcProtocolFelica); - Felica_PollerContext* poller_context = context; + FelicaPollerContext* poller_context = context; FelicaPoller* felica_poller = event.instance; FelicaPollerEvent* felica_event = event.event_data; if(felica_event->type == FelicaPollerEventTypeReady || felica_event->type == FelicaPollerEventTypeIncomplete) { - felica_copy(&poller_context->data, felica_poller->data); + felica_copy(poller_context->data.data, felica_poller->data); } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth; memcpy( @@ -45,7 +45,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard furi_check(nfc); furi_check(data); - Felica_PollerContext poller_context = {}; + FelicaPollerContext poller_context = {}; if(card_key == NULL) { poller_context.auth_ctx.skip_auth = true; } else { @@ -54,6 +54,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard } poller_context.thread_id = furi_thread_get_current_id(); + poller_context.data.data = felica_alloc(); NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolFelica); nfc_poller_start(poller, felica_poller_read_callback, &poller_context); furi_thread_flags_wait(FELICA_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever); @@ -63,8 +64,10 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard nfc_poller_free(poller); if(poller_context.error == FelicaErrorNone) { - *data = poller_context.data; + felica_copy(data, poller_context.data.data); } + felica_free(poller_context.data.data); + return poller_context.error; } diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index c8a241337..b27c55c8f 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -192,7 +192,7 @@ SubGhzProtocolStatus } uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF; } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index 70ba6c586..512dd4744 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -189,7 +189,7 @@ SubGhzProtocolStatus } uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { - key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF; } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Key"); diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index f1c41bd1e..60599ca23 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -249,13 +249,8 @@ static void subghz_protocol_scher_khan_check_remote_controller( instance->btn = 0; instance->cnt = 0; break; - case 81: //MAGIC CODE PRO / PRO2 Response ??? - *protocol_name = "MAGIC CODE PRO,\n Response"; - instance->serial = 0; - instance->btn = 0; - instance->cnt = 0; - break; - case 82: //MAGIC CODE PRO / PRO2 Response ??? + case 81: // MAGIC CODE PRO / PRO2 Response ??? + case 82: // MAGIC CODE PRO / PRO2 Response ??? *protocol_name = "MAGIC CODE PRO,\n Response"; instance->serial = 0; instance->btn = 0; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index de385684d..15a3d360b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1070,7 +1070,9 @@ Function,+,felica_crc_trim,void,BitBuffer* Function,+,felica_free,void,FelicaData* Function,+,felica_get_base_data,FelicaData*,const FelicaData* Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" +Function,+,felica_get_ic_name,void,"const FelicaData*, FuriString*" Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" +Function,+,felica_get_workflow_type,void,FelicaData* Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" @@ -1078,8 +1080,10 @@ Function,+,felica_poller_read_blocks,FelicaError,"FelicaPoller*, const uint8_t, Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*" Function,+,felica_reset,void,FelicaData* Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" +Function,+,felica_service_get_attribute_string,void,"const FelicaService*, FuriString*" Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" +Function,+,felica_write_directory_tree,void,"const FelicaData*, FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE*