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*