diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc new file mode 100644 index 000000000..93ba4ba6c --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Felica.nfc @@ -0,0 +1,40 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: FeliCa +# UID is common for all formats +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 +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 +Block 1: 00 00 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF +Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 7: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 9: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 12: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 14: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF +Block 15: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 16: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 17: 00 00 29 9F FA 53 AB 75 87 6E 57 4E 10 2A 94 16 BC 8E +Block 18: 00 00 29 9F FA 53 AB 75 87 6E 00 F1 00 00 00 01 43 00 +Block 19: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 20: 00 00 88 B4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 21: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 22: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF +Block 23: 00 00 FF FF FF 00 FF 00 10 00 00 00 00 00 00 00 00 00 +Block 24: 00 00 24 FE FF 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 25: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 26: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 27: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 94fe53632..1e4407076 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -646,6 +648,56 @@ MU_TEST(mf_classic_dict_test) { "Remove test dict failed"); } +static FelicaError + felica_do_request_response(FelicaData* felica_data, const FelicaCardKey* card_key) { + NfcDeviceData* nfc_device = nfc_device_alloc(); + + FelicaError error = FelicaErrorNone; + if(!nfc_device_load(nfc_device, EXT_PATH("unit_tests/nfc/Felica.nfc"))) { + error = FelicaErrorNotPresent; + } else { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + NfcListener* felica_listener = nfc_listener_alloc( + listener, NfcProtocolFelica, nfc_device_get_data(nfc_device, NfcProtocolFelica)); + nfc_listener_start(felica_listener, NULL, NULL); + + error = felica_poller_sync_read(poller, felica_data, card_key); + + nfc_listener_stop(felica_listener); + nfc_listener_free(felica_listener); + + nfc_free(listener); + nfc_free(poller); + } + + nfc_device_free(nfc_device); + return error; +} + +MU_TEST(felica_read) { + FelicaData* felica_data = felica_alloc(); + FelicaError error = felica_do_request_response(felica_data, NULL); + mu_assert(error == FelicaErrorNone, "felica_poller() failed"); + mu_assert(felica_data->data.fs.spad[4].SF1 == 0x01, "block[4].SF1 != 0x01"); + mu_assert(felica_data->data.fs.spad[4].SF2 == 0xB1, "block[4].SF2 != 0xB1"); + + felica_free(felica_data); +} + +MU_TEST(felica_read_auth) { + FelicaData* felica_data = felica_alloc(); + FelicaCardKey card_key; + memset(card_key.data, 0xFF, FELICA_DATA_BLOCK_SIZE); + + FelicaError error = felica_do_request_response(felica_data, &card_key); + mu_assert(error == FelicaErrorNone, "felica_poller() failed"); + mu_assert(felica_data->data.fs.spad[4].SF1 == 0x00, "block[4].SF1 != 0x00"); + mu_assert(felica_data->data.fs.spad[4].SF2 == 0x00, "block[4].SF2 != 0x00"); + + felica_free(felica_data); +} + MU_TEST(slix_file_with_capabilities_test) { NfcDevice* nfc_device_missed_cap = nfc_device_alloc(); mu_assert( @@ -807,6 +859,8 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_value_block); MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); + MU_RUN_TEST(felica_read); + MU_RUN_TEST(felica_read_auth); MU_RUN_TEST(slix_file_with_capabilities_test); MU_RUN_TEST(slix_set_password_default_cap_correct_pass); diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index b0660f3e6..ceb180653 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -26,12 +26,6 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { widget_add_text_scroll_element( instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str)); - widget_add_button_element( - instance->widget, - GuiButtonTypeRight, - "More", - nfc_protocol_support_common_widget_callback, - instance); furi_string_free(temp_str); } @@ -177,7 +171,7 @@ static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEv } const NfcProtocolSupportBase nfc_protocol_support_felica = { - .features = NfcProtocolFeatureEmulateUid, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, .scene_info = { diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 0d748a80b..48bfc2cbe 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -52,6 +52,7 @@ typedef enum { SubGhzRxKeyStateAddKey, SubGhzRxKeyStateExit, SubGhzRxKeyStateRAWLoad, + SubGhzRxKeyStateRAWMore, SubGhzRxKeyStateRAWSave, } SubGhzRxKeyState; diff --git a/applications/main/subghz/scenes/subghz_scene_delete_raw.c b/applications/main/subghz/scenes/subghz_scene_delete_raw.c index ec6b93653..6fe304c4a 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_raw.c @@ -66,7 +66,13 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { if(event.event == SubGhzCustomEventSceneDeleteRAW) { furi_string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); + if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateRAWLoad) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); + } else { + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); + } + } else { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 2a5807d7c..4c1320f02 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -80,6 +80,7 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", threshold_rssi); break; case SubGhzRxKeyStateRAWLoad: + case SubGhzRxKeyStateRAWMore: path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, @@ -101,7 +102,8 @@ void subghz_scene_read_raw_on_enter(void* context) { break; } - if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) { + if((subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) && + (subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateRAWLoad)) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); if(furi_string_empty(file_name)) { @@ -198,7 +200,9 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { if(subghz_scene_read_raw_update_filename(subghz)) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); - subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad); + if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateRAWLoad) { + subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWMore); + } scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); consumed = true; } else { diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 151d73d61..306b25777 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -50,7 +51,6 @@ static void rpc_system_storage_reset_state( if(rpc_storage->state == RpcStorageStateWriting) { storage_file_close(rpc_storage->file); storage_file_free(rpc_storage->file); - furi_record_close(RECORD_STORAGE); } rpc_storage->state = RpcStorageStateIdle; @@ -118,10 +118,8 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; - Storage* fs_api = furi_record_open(RECORD_STORAGE); - FS_Error error = storage_common_fs_info( - fs_api, + rpc_storage->api, request->content.storage_info_request.path, &response->content.storage_info_response.total_space, &response->content.storage_info_response.free_space); @@ -135,7 +133,6 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex rpc_send_and_release(session, response); free(response); - furi_record_close(RECORD_STORAGE); } static void rpc_system_storage_timestamp_process(const PB_Main* request, void* context) { @@ -154,11 +151,9 @@ static void rpc_system_storage_timestamp_process(const PB_Main* request, void* c PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; - Storage* fs_api = furi_record_open(RECORD_STORAGE); - const char* path = request->content.storage_timestamp_request.path; uint32_t timestamp = 0; - FS_Error error = storage_common_timestamp(fs_api, path, ×tamp); + FS_Error error = storage_common_timestamp(rpc_storage->api, path, ×tamp); response->command_status = rpc_system_storage_get_error(error); response->which_content = PB_Main_empty_tag; @@ -170,7 +165,6 @@ static void rpc_system_storage_timestamp_process(const PB_Main* request, void* c rpc_send_and_release(session, response); free(response); - furi_record_close(RECORD_STORAGE); } static void rpc_system_storage_stat_process(const PB_Main* request, void* context) { @@ -189,11 +183,9 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; - Storage* fs_api = furi_record_open(RECORD_STORAGE); - const char* path = request->content.storage_stat_request.path; FileInfo fileinfo; - FS_Error error = storage_common_stat(fs_api, path, &fileinfo); + FS_Error error = storage_common_stat(rpc_storage->api, path, &fileinfo); response->command_status = rpc_system_storage_get_error(error); response->which_content = PB_Main_empty_tag; @@ -209,12 +201,12 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex rpc_send_and_release(session, response); free(response); - furi_record_close(RECORD_STORAGE); } static void rpc_system_storage_list_root(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); + RpcStorageSystem* rpc_storage = context; RpcSession* session = rpc_storage->session; furi_assert(session); @@ -279,8 +271,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex return; } - Storage* fs_api = furi_record_open(RECORD_STORAGE); - File* dir = storage_file_alloc(fs_api); + File* dir = storage_file_alloc(rpc_storage->api); PB_Main response = { .command_id = request->command_id, @@ -293,7 +284,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex bool include_md5 = list_request->include_md5; FuriString* md5 = furi_string_alloc(); FuriString* md5_path = furi_string_alloc(); - File* file = storage_file_alloc(fs_api); + File* file = storage_file_alloc(rpc_storage->api); bool finish = false; int i = 0; @@ -350,8 +341,6 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex storage_dir_close(dir); storage_file_free(dir); storage_file_free(file); - - furi_record_close(RECORD_STORAGE); } static void rpc_system_storage_read_process(const PB_Main* request, void* context) { @@ -370,8 +359,7 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex /* use same message memory to send response */ PB_Main* response = malloc(sizeof(PB_Main)); const char* path = request->content.storage_read_request.path; - Storage* fs_api = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(fs_api); + File* file = storage_file_alloc(rpc_storage->api); bool fs_operation_success = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); if(fs_operation_success) { @@ -420,8 +408,6 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex free(response); storage_file_close(file); storage_file_free(file); - - furi_record_close(RECORD_STORAGE); } static void rpc_system_storage_write_process(const PB_Main* request, void* context) { @@ -451,7 +437,6 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte } if(rpc_storage->state != RpcStorageStateWriting) { - rpc_storage->api = furi_record_open(RECORD_STORAGE); rpc_storage->file = storage_file_alloc(rpc_storage->api); rpc_storage->current_command_id = request->command_id; rpc_storage->state = RpcStorageStateWriting; @@ -492,14 +477,15 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte } } -static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) { - furi_assert(fs_api); +static bool rpc_system_storage_is_dir_is_empty(Storage* storage, const char* path) { + furi_assert(storage); furi_assert(path); + FileInfo fileinfo; bool is_dir_is_empty = true; - FS_Error error = storage_common_stat(fs_api, path, &fileinfo); + FS_Error error = storage_common_stat(storage, path, &fileinfo); if((error == FSE_OK) && file_info_is_dir(&fileinfo)) { - File* dir = storage_file_alloc(fs_api); + File* dir = storage_file_alloc(storage); if(storage_dir_open(dir, path)) { char* name = malloc(MAX_NAME_LENGTH); while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { @@ -531,18 +517,17 @@ static void rpc_system_storage_delete_process(const PB_Main* request, void* cont PB_CommandStatus status = PB_CommandStatus_ERROR; rpc_system_storage_reset_state(rpc_storage, session, true); - Storage* fs_api = furi_record_open(RECORD_STORAGE); - char* path = request->content.storage_delete_request.path; if(!path) { status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; } else { - FS_Error error_remove = storage_common_remove(fs_api, path); + FS_Error error_remove = storage_common_remove(rpc_storage->api, path); // FSE_DENIED is for empty directory, but not only for this // that's why we have to check it - if((error_remove == FSE_DENIED) && !rpc_system_storage_is_dir_is_empty(fs_api, path)) { + if((error_remove == FSE_DENIED) && + !rpc_system_storage_is_dir_is_empty(rpc_storage->api, path)) { if(request->content.storage_delete_request.recursive) { - bool deleted = storage_simply_remove_recursive(fs_api, path); + bool deleted = storage_simply_remove_recursive(rpc_storage->api, path); status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR; } else { status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY; @@ -554,7 +539,6 @@ static void rpc_system_storage_delete_process(const PB_Main* request, void* cont } } - furi_record_close(RECORD_STORAGE); rpc_send_and_release_empty(session, request->command_id, status); } @@ -572,11 +556,10 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte PB_CommandStatus status; rpc_system_storage_reset_state(rpc_storage, session, true); - Storage* fs_api = furi_record_open(RECORD_STORAGE); char* path = request->content.storage_mkdir_request.path; if(path) { if(path_contains_only_ascii(path)) { - FS_Error error = storage_common_mkdir(fs_api, path); + FS_Error error = storage_common_mkdir(rpc_storage->api, path); status = rpc_system_storage_get_error(error); } else { status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME; @@ -584,7 +567,6 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte } else { status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; } - furi_record_close(RECORD_STORAGE); rpc_send_and_release_empty(session, request->command_id, status); } @@ -608,8 +590,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont return; } - Storage* fs_api = furi_record_open(RECORD_STORAGE); - File* file = storage_file_alloc(fs_api); + File* file = storage_file_alloc(rpc_storage->api); FuriString* md5 = furi_string_alloc(); FS_Error file_error; @@ -633,8 +614,6 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont furi_string_free(md5); storage_file_free(file); - - furi_record_close(RECORD_STORAGE); } static void rpc_system_storage_rename_process(const PB_Main* request, void* context) { @@ -651,11 +630,9 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont PB_CommandStatus status; rpc_system_storage_reset_state(rpc_storage, session, true); - Storage* fs_api = furi_record_open(RECORD_STORAGE); - if(path_contains_only_ascii(request->content.storage_rename_request.new_path)) { FS_Error error = storage_common_rename( - fs_api, + rpc_storage->api, request->content.storage_rename_request.old_path, request->content.storage_rename_request.new_path); status = rpc_system_storage_get_error(error); @@ -663,7 +640,6 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME; } - furi_record_close(RECORD_STORAGE); rpc_send_and_release_empty(session, request->command_id, status); } @@ -678,12 +654,10 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi RpcSession* session = rpc_storage->session; furi_assert(session); - Storage* fs_api = furi_record_open(RECORD_STORAGE); + rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = - lfs_backup_create(fs_api, request->content.storage_backup_create_request.archive_path); - - furi_record_close(RECORD_STORAGE); + bool backup_ok = lfs_backup_create( + rpc_storage->api, request->content.storage_backup_create_request.archive_path); rpc_send_and_release_empty( session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR); @@ -700,17 +674,58 @@ static void rpc_system_storage_backup_restore_process(const PB_Main* request, vo RpcSession* session = rpc_storage->session; furi_assert(session); - Storage* fs_api = furi_record_open(RECORD_STORAGE); + rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = - lfs_backup_unpack(fs_api, request->content.storage_backup_restore_request.archive_path); - - furi_record_close(RECORD_STORAGE); + bool backup_ok = lfs_backup_unpack( + rpc_storage->api, request->content.storage_backup_restore_request.archive_path); rpc_send_and_release_empty( session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR); } +static void rpc_system_storage_tar_extract_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_storage_tar_extract_request_tag); + furi_assert(context); + + FURI_LOG_D(TAG, "TarExtract"); + + RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + + PB_CommandStatus status; + rpc_system_storage_reset_state(rpc_storage, session, true); + + TarArchive* archive = tar_archive_alloc(rpc_storage->api); + + do { + if(!path_contains_only_ascii(request->content.storage_tar_extract_request.out_path)) { + status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME; + break; + } + + if(!tar_archive_open( + archive, + request->content.storage_tar_extract_request.tar_path, + TAR_OPEN_MODE_READ)) { + status = PB_CommandStatus_ERROR_STORAGE_INVALID_PARAMETER; + break; + } + + if(!tar_archive_unpack_to( + archive, request->content.storage_tar_extract_request.out_path, NULL)) { + status = PB_CommandStatus_ERROR_STORAGE_INTERNAL; + break; + } + + status = PB_CommandStatus_OK; + } while(0); + + tar_archive_free(archive); + rpc_send_and_release_empty(session, request->command_id, status); +} + void* rpc_system_storage_alloc(RpcSession* session) { furi_assert(session); @@ -761,6 +776,9 @@ void* rpc_system_storage_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_storage_backup_restore_process; rpc_add_handler(session, PB_Main_storage_backup_restore_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_storage_tar_extract_process; + rpc_add_handler(session, PB_Main_storage_tar_extract_request_tag, &rpc_handler); + return rpc_storage; } @@ -771,5 +789,8 @@ void rpc_system_storage_free(void* context) { furi_assert(session); rpc_system_storage_reset_state(rpc_storage, session, false); + + furi_record_close(RECORD_STORAGE); + rpc_storage->api = NULL; free(rpc_storage); } diff --git a/assets/protobuf b/assets/protobuf index 1956b83bb..816de200a 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 1956b83bba99313ee8d8386e5d35d0549341ca26 +Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index d30467740..02b1f09f8 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -45,11 +45,13 @@ env.Append( File("protocols/iso14443_4a/iso14443_4a_listener.h"), File("protocols/mf_ultralight/mf_ultralight_listener.h"), File("protocols/mf_classic/mf_classic_listener.h"), + File("protocols/felica/felica_listener.h"), # Sync API File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), File("protocols/mf_classic/mf_classic_poller_sync.h"), File("protocols/st25tb/st25tb_poller_sync.h"), + File("protocols/felica/felica_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/helpers/felica_crc.c b/lib/nfc/helpers/felica_crc.c index d5e0853a1..bba7cee34 100644 --- a/lib/nfc/helpers/felica_crc.c +++ b/lib/nfc/helpers/felica_crc.c @@ -6,6 +6,8 @@ #define FELICA_CRC_INIT (0x0000U) uint16_t felica_crc_calculate(const uint8_t* data, size_t length) { + furi_check(data); + uint16_t crc = FELICA_CRC_INIT; for(size_t i = 0; i < length; i++) { @@ -24,6 +26,7 @@ uint16_t felica_crc_calculate(const uint8_t* data, size_t length) { } void felica_crc_append(BitBuffer* buf) { + furi_check(buf); const uint8_t* data = bit_buffer_get_data(buf); const size_t data_size = bit_buffer_get_size_bytes(buf); @@ -32,6 +35,7 @@ void felica_crc_append(BitBuffer* buf) { } bool felica_crc_check(const BitBuffer* buf) { + furi_check(buf); const size_t data_size = bit_buffer_get_size_bytes(buf); if(data_size <= FELICA_CRC_SIZE) return false; @@ -45,6 +49,7 @@ bool felica_crc_check(const BitBuffer* buf) { } void felica_crc_trim(BitBuffer* buf) { + furi_check(buf); const size_t data_size = bit_buffer_get_size_bytes(buf); furi_assert(data_size > FELICA_CRC_SIZE); diff --git a/lib/nfc/nfc_mock.c b/lib/nfc/nfc_mock.c index df0e009e7..a17f0e104 100644 --- a/lib/nfc/nfc_mock.c +++ b/lib/nfc/nfc_mock.c @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include @@ -50,11 +53,31 @@ typedef struct { Iso14443_3aSelResp sel_resp[2]; } Iso14443_3aColResData; +typedef struct { + uint8_t length; + uint8_t polling_cmd; + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; +} FelicaPollingRequest; + +typedef struct { + uint8_t code; + FelicaIDm idm; + FelicaPMm pmm; +} FelicaSensfResData; + +typedef struct { + uint16_t system_code; + FelicaSensfResData sens_res; +} FelicaPTMemory; + struct Nfc { NfcState state; Iso14443_3aColResStatus col_res_status; Iso14443_3aColResData col_res_data; + FelicaPTMemory pt_memory; bool software_col_res_required; NfcEventCallback callback; @@ -243,6 +266,21 @@ static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, ui NfcEvent event = {.type = NfcEventTypeListenerActivated}; instance->callback(event, instance->context); + processed = true; + } + } else if(rx_bits == 8 * 8) { + FelicaPollingRequest* request = (FelicaPollingRequest*)rx_data; + if(request->system_code == instance->pt_memory.system_code) { + uint8_t response_size = sizeof(FelicaSensfResData) + 1; + bit_buffer_reset(tx_buffer); + bit_buffer_append_byte(tx_buffer, response_size); + bit_buffer_append_bytes( + tx_buffer, (uint8_t*)&instance->pt_memory.sens_res, sizeof(FelicaSensfResData)); + felica_crc_append(tx_buffer); + nfc_listener_tx(instance, tx_buffer); + instance->col_res_status = Iso14443_3aColResStatusDone; + NfcEvent event = {.type = NfcEventTypeListenerActivated}; + instance->callback(event, instance->context); processed = true; } } @@ -470,6 +508,12 @@ NfcError nfc_felica_listener_set_sensf_res_data( furi_assert(idm_len == 8); furi_assert(pmm_len == 8); + instance->pt_memory.system_code = 0xFFFF; + instance->pt_memory.sens_res.code = 0x01; + instance->software_col_res_required = true; + memcpy(instance->pt_memory.sens_res.idm.data, idm, idm_len); + memcpy(instance->pt_memory.sens_res.pmm.data, pmm, pmm_len); + return NfcErrorNone; } diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index bc47e0642..89e610f86 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -298,15 +298,33 @@ bool felica_check_mac( furi_check(blocks); furi_check(data); - uint8_t first_block[8]; uint8_t mac[8]; - felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block); + felica_calculate_mac_read(ctx, session_key, rc, blocks, block_count, data, mac); + uint8_t mac_offset = FELICA_DATA_BLOCK_SIZE * (block_count - 1); + uint8_t* mac_ptr = data + mac_offset; + return !memcmp(mac, mac_ptr, 8); +} + +void felica_calculate_mac_read( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + const uint8_t* data, + uint8_t* mac) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(blocks); + furi_check(data); + furi_check(mac); + + uint8_t first_block[8]; + felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block); uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1); felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac); - - uint8_t* mac_ptr = data + data_size_without_mac; - return !memcmp(mac, mac_ptr, 8); } void felica_calculate_mac_write( diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index d032943d3..17229c150 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -12,7 +12,13 @@ extern "C" { #define FELICA_PMM_SIZE (8U) #define FELICA_DATA_BLOCK_SIZE (16U) -#define FELICA_BLOCKS_TOTAL_COUNT (27U) +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U) + +#define FELICA_SERVICE_RW_ACCESS (0x0009U) +#define FELICA_SERVICE_RO_ACCESS (0x000BU) + +#define FELICA_BLOCKS_TOTAL_COUNT (28U) #define FELICA_BLOCK_INDEX_REG (0x0EU) #define FELICA_BLOCK_INDEX_RC (0x80U) #define FELICA_BLOCK_INDEX_MAC (0x81U) @@ -54,6 +60,10 @@ typedef enum { FelicaErrorTimeout, } FelicaError; +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaBlockData; + /** @brief Separate type for card key block. Used in authentication process */ typedef struct { uint8_t data[FELICA_DATA_BLOCK_SIZE]; @@ -76,6 +86,22 @@ typedef struct { FelicaAuthenticationStatus auth_status; /**< Authentication status*/ } FelicaAuthenticationContext; +/** + * @brief Stucture for holding Felica session key which is calculated from rc and ck. +*/ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaSessionKey; + +/** + * @brief Structure used to hold authentication related fields. +*/ +typedef struct { + mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */ + FelicaSessionKey session_key; /**< Calculated session key. */ + FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */ +} FelicaAuthentication; + /** @brief Felica ID block */ typedef struct { uint8_t data[FELICA_IDM_SIZE]; @@ -128,6 +154,51 @@ typedef struct { FelicaFSUnion data; } FelicaData; +#pragma pack(push, 1) +typedef struct { + uint8_t code; + FelicaIDm idm; + uint8_t service_num; + uint16_t service_code; + uint8_t block_count; +} FelicaCommandHeader; +#pragma pack(pop) + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; +} FelicaCommandResponseHeader; + +typedef struct { + uint8_t service_code : 4; + uint8_t access_mode : 3; + uint8_t length : 1; + uint8_t block_number; +} FelicaBlockListElement; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; + uint8_t block_count; + uint8_t data[]; +} FelicaPollerReadCommandResponse; + +typedef struct { + FelicaCommandResponseHeader header; + uint8_t block_count; + uint8_t data[]; +} FelicaListenerReadCommandResponse; + +typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse; + +typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse; + extern const NfcDeviceBase nfc_device_felica; FelicaData* felica_alloc(void); @@ -168,6 +239,15 @@ bool felica_check_mac( const uint8_t block_count, uint8_t* data); +void felica_calculate_mac_read( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + const uint8_t* data, + uint8_t* mac); + void felica_calculate_mac_write( mbedtls_des3_context* ctx, const uint8_t* session_key, diff --git a/lib/nfc/protocols/felica/felica_listener.c b/lib/nfc/protocols/felica/felica_listener.c index 4e6c05785..90215eced 100644 --- a/lib/nfc/protocols/felica/felica_listener.c +++ b/lib/nfc/protocols/felica/felica_listener.c @@ -1,9 +1,14 @@ #include "felica_listener_i.h" #include "nfc/protocols/nfc_listener_base.h" +#include +#include -#define FELICA_LISTENER_MAX_BUFFER_SIZE (64) -#define TAG "Felica" +#define FELICA_LISTENER_MAX_BUFFER_SIZE (128) +#define FELICA_LISTENER_RESPONSE_CODE_READ (0x07) +#define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09) + +#define TAG "FelicaListener" FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { furi_assert(nfc); @@ -15,8 +20,11 @@ FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) { instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE); + mbedtls_des3_init(&instance->auth.des_context); nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC); + memcpy(instance->mc_shadow.data, instance->data->data.fs.mc.data, FELICA_DATA_BLOCK_SIZE); + instance->data->data.fs.state.data[0] = 0; nfc_config(instance->nfc, NfcModeListener, NfcTechFelica); nfc_felica_listener_set_sensf_res_data( nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm)); @@ -49,6 +57,99 @@ const FelicaData* felica_listener_get_data(const FelicaListener* instance) { return instance->data; } +static FelicaError felica_listener_command_handler_read( + FelicaListener* instance, + const FelicaListenerGenericRequest* const generic_request) { + const FelicaListenerReadRequest* request = (FelicaListenerReadRequest*)generic_request; + FURI_LOG_D(TAG, "Read cmd"); + + FelicaListenerReadCommandResponse* resp = malloc( + sizeof(FelicaCommandResponseHeader) + 1 + + FELICA_LISTENER_READ_BLOCK_COUNT_MAX * FELICA_DATA_BLOCK_SIZE); + + resp->header.response_code = FELICA_LISTENER_RESPONSE_CODE_READ; + resp->header.idm = request->base.header.idm; + resp->header.length = sizeof(FelicaCommandResponseHeader); + + if(felica_listener_validate_read_request_and_set_sf(instance, request, &resp->header)) { + resp->block_count = request->base.header.block_count; + resp->header.length++; + } else { + resp->block_count = 0; + } + + instance->mac_calc_start = 0; + memset(instance->requested_blocks, 0, sizeof(instance->requested_blocks)); + const FelicaBlockListElement* item = + felica_listener_block_list_item_get_first(instance, request); + for(uint8_t i = 0; i < resp->block_count; i++) { + instance->requested_blocks[i] = item->block_number; + FelicaCommanReadBlockHandler handler = + felica_listener_get_read_block_handler(item->block_number); + + handler(instance, item->block_number, i, resp); + + item = felica_listener_block_list_item_get_next(instance, item); + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length); + free(resp); + + return felica_listener_frame_exchange(instance, instance->tx_buffer); +} + +static FelicaError felica_listener_command_handler_write( + FelicaListener* instance, + const FelicaListenerGenericRequest* const generic_request) { + FURI_LOG_D(TAG, "Write cmd"); + + const FelicaListenerWriteRequest* request = (FelicaListenerWriteRequest*)generic_request; + const FelicaListenerWriteBlockData* data_ptr = + felica_listener_get_write_request_data_pointer(instance, generic_request); + + FelicaListenerWriteCommandResponse* resp = malloc(sizeof(FelicaListenerWriteCommandResponse)); + + resp->response_code = FELICA_LISTENER_RESPONSE_CODE_WRITE; + resp->idm = request->base.header.idm; + resp->length = sizeof(FelicaListenerWriteCommandResponse); + + if(felica_listener_validate_write_request_and_set_sf(instance, request, data_ptr, resp)) { + const FelicaBlockListElement* item = + felica_listener_block_list_item_get_first(instance, request); + for(uint8_t i = 0; i < request->base.header.block_count; i++) { + FelicaCommandWriteBlockHandler handler = + felica_listener_get_write_block_handler(item->block_number); + + handler(instance, item->block_number, &data_ptr->blocks[i]); + + item = felica_listener_block_list_item_get_next(instance, item); + } + felica_wcnt_increment(instance->data); + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->length); + free(resp); + + return felica_listener_frame_exchange(instance, instance->tx_buffer); +} + +static FelicaError felica_listener_process_request( + FelicaListener* instance, + const FelicaListenerGenericRequest* generic_request) { + const uint8_t cmd_code = generic_request->header.code; + switch(cmd_code) { + case FELICA_CMD_READ_WITHOUT_ENCRYPTION: + return felica_listener_command_handler_read(instance, generic_request); + case FELICA_CMD_WRITE_WITHOUT_ENCRYPTION: + return felica_listener_command_handler_write(instance, generic_request); + default: + FURI_LOG_E(TAG, "FeliCa incorrect command"); + return FelicaErrorNotPresent; + } +} + NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -58,14 +159,44 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) { NfcEvent* nfc_event = event.event_data; NfcCommand command = NfcCommandContinue; - if(nfc_event->type == NfcEventTypeListenerActivated) { + if(nfc_event->type == NfcEventTypeFieldOn) { + FURI_LOG_D(TAG, "Field On"); + } else if(nfc_event->type == NfcEventTypeListenerActivated) { instance->state = Felica_ListenerStateActivated; FURI_LOG_D(TAG, "Activated"); } else if(nfc_event->type == NfcEventTypeFieldOff) { instance->state = Felica_ListenerStateIdle; FURI_LOG_D(TAG, "Field Off"); + felica_listener_reset(instance); } else if(nfc_event->type == NfcEventTypeRxEnd) { FURI_LOG_D(TAG, "Rx Done"); + do { + if(!felica_crc_check(nfc_event->data.buffer)) { + FURI_LOG_E(TAG, "Wrong CRC"); + break; + } + + FelicaListenerGenericRequest* request = + (FelicaListenerGenericRequest*)bit_buffer_get_data(nfc_event->data.buffer); + + uint8_t size = bit_buffer_get_size_bytes(nfc_event->data.buffer) - 2; + if((request->length != size) || + (!felica_listener_check_block_list_size(instance, request))) { + FURI_LOG_E(TAG, "Wrong request length"); + break; + } + + if(!felica_listener_check_idm(instance, &request->header.idm)) { + FURI_LOG_E(TAG, "Wrong IDm"); + break; + } + + FelicaError error = felica_listener_process_request(instance, request); + if(error != FelicaErrorNone) { + FURI_LOG_E(TAG, "Processing error: %2X", error); + } + } while(false); + bit_buffer_reset(nfc_event->data.buffer); } return command; } diff --git a/lib/nfc/protocols/felica/felica_listener_i.c b/lib/nfc/protocols/felica/felica_listener_i.c new file mode 100644 index 000000000..caab11778 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_listener_i.c @@ -0,0 +1,738 @@ +#include "felica_listener_i.h" + +#include + +#define FELICA_WCNT_MC2_FF_MAX_VALUE (0x00FFFFFFU) +#define FELICA_WCNT_MC2_00_MAX_VALUE (0x00FFFE00U) +#define FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE (0x00001027U) +#define FELICA_WCNT_MC2_00_WARNING_END_VALUE (0x00FFFDFFU) + +#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U) +#define FELICA_MC_ALL_BYTE (2U) +#define FELICA_MC_SYS_OP (3U) +#define FELICA_MC_RF_PRM (4U) +#define FELICA_MC_CKCKV_W_MAC_A (5U) +#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U) +#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U) +#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U) +#define FELICA_MC_STATE_W_MAC_A (12U) +#define FELICA_MC_RESERVED_13 (13U) +#define FELICA_MC_RESERVED_14 (14U) +#define FELICA_MC_RESERVED_15 (15U) + +#define FELICA_MC_BYTE_GET(data, byte) (data->data.fs.mc.data[byte]) +#define FELICA_SYSTEM_BLOCK_RO_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0x00) +#define FELICA_SYSTEM_BLOCK_RW_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0xFF) + +#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN ((uint8_t)sizeof(FelicaBlockListElement)) +#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item) \ + ((item->length == 1) ? FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN : \ + (FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN + 1)) + +static uint32_t felica_wcnt_get_max_value(const FelicaData* data) { + if(!FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && !FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) { + furi_crash("MC[2] reserved values are forbidden"); + } + return (FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) ? FELICA_WCNT_MC2_FF_MAX_VALUE : + FELICA_WCNT_MC2_00_MAX_VALUE; +} + +static bool felica_wcnt_check_warning_boundary(const FelicaData* data) { + const uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data; + return ( + FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && + ((*wcnt_ptr > FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE) && + (*wcnt_ptr < FELICA_WCNT_MC2_00_WARNING_END_VALUE))); +} + +static bool felica_wcnt_check_error_boundary(const FelicaData* data) { + const uint32_t wcnt_max = felica_wcnt_get_max_value(data); + const uint32_t* wcnt_ptr = (const uint32_t*)data->data.fs.wcnt.data; + return *wcnt_ptr != wcnt_max; +} + +void felica_wcnt_increment(FelicaData* data) { + furi_assert(data); + + const uint32_t wcnt_max = felica_wcnt_get_max_value(data); + uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data; + if(*wcnt_ptr < wcnt_max) { + *wcnt_ptr += 1; + } +} + +static void felica_wcnt_post_process(FelicaData* data) { + uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data; + + if(FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && (*wcnt_ptr > FELICA_WCNT_MC2_00_MAX_VALUE)) { + *wcnt_ptr = 0; + } +} + +static bool + felica_listener_block_list_item_validate_block_number(const FelicaBlockListElement* item) { + bool valid = true; + if(item->length == 0) { + uint8_t D2_block_number = *(&item->block_number + 1); + valid = D2_block_number == 0; + } + return valid; +} + +static const FelicaBlockListElement* felica_listener_block_list_iterate( + FelicaListener* instance, + const FelicaBlockListElement* prev_item, + const uint8_t item_size) { + FelicaBlockListElement* next_item = NULL; + if(instance->request_size_buf >= FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN) { + next_item = (FelicaBlockListElement*)((uint8_t*)prev_item + item_size); + + uint8_t next_item_size = FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(next_item); + + if(instance->request_size_buf < next_item_size) { + next_item = NULL; + instance->request_size_buf = 0; + } else { + instance->request_size_buf -= next_item_size; + } + } + + return next_item; +} + +const FelicaBlockListElement* felica_listener_block_list_item_get_first( + FelicaListener* instance, + const FelicaListenerRequest* request) { + furi_assert(instance); + furi_assert(request); + + instance->request_size_buf = request->base.length - sizeof(FelicaListenerGenericRequest); + return felica_listener_block_list_iterate(instance, request->list, 0); +} + +const FelicaBlockListElement* felica_listener_block_list_item_get_next( + FelicaListener* instance, + const FelicaBlockListElement* prev_item) { + furi_assert(instance); + furi_assert(prev_item); + + return felica_listener_block_list_iterate( + instance, prev_item, FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(prev_item)); +} + +const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer( + const FelicaListener* const instance, + const FelicaListenerGenericRequest* const generic_request) { + furi_assert(instance); + furi_assert(generic_request); + + return (const FelicaListenerWriteBlockData*)((uint8_t*)generic_request + + sizeof(FelicaListenerGenericRequest) + + instance->block_list_size); +} + +static bool felica_listener_check_write_request_data_size( + const FelicaListener* const instance, + const FelicaListenerRequest* request, + const uint8_t fact_item_cnt) { + uint8_t possible_data_size = fact_item_cnt * FELICA_DATA_BLOCK_SIZE; + uint8_t fact_data_size = + request->base.length - sizeof(FelicaListenerGenericRequest) - instance->block_list_size; + return (possible_data_size <= fact_data_size); +} + +static bool felica_listener_test_block_list_size_bounds(const FelicaListenerGenericRequest* req) { + bool valid = false; + if(req->header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) { + valid = + (req->header.block_count >= FELICA_LISTENER_READ_BLOCK_COUNT_MIN && + req->header.block_count <= FELICA_LISTENER_READ_BLOCK_COUNT_MAX); + } else if(req->header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) { + valid = + (req->header.block_count >= FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN && + req->header.block_count <= FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX); + } + + return valid; +} + +static uint8_t felica_listener_get_block_list_item_count_size( + FelicaListener* instance, + FelicaListenerRequest* request) { + uint8_t list_size = 0; + uint8_t item_cnt = 0; + + uint8_t max_item_cnt = (request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) ? + FELICA_LISTENER_READ_BLOCK_COUNT_MAX : + FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX; + + const FelicaBlockListElement* item = + felica_listener_block_list_item_get_first(instance, request); + + while((item != NULL) && (item_cnt < max_item_cnt)) { + item_cnt++; + + if(request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) { + if(instance->request_size_buf >= FELICA_DATA_BLOCK_SIZE) { + instance->request_size_buf -= FELICA_DATA_BLOCK_SIZE; + } else { + instance->request_size_buf = 0; + } + } + + list_size += FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item); + item = felica_listener_block_list_item_get_next(instance, item); + } + + instance->block_list_size = list_size; + return item_cnt; +} + +bool felica_listener_check_block_list_size( + FelicaListener* instance, + FelicaListenerGenericRequest* req) { + furi_assert(instance); + furi_assert(req); + + FelicaListenerRequest* request = (FelicaListenerRequest*)req; + bool valid = true; + + uint8_t item_cnt = felica_listener_get_block_list_item_count_size(instance, request); + valid = (item_cnt > 0); + + if(felica_listener_test_block_list_size_bounds(req) && + (item_cnt < request->base.header.block_count)) { + valid = false; + } + + if(valid && request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) { + valid &= felica_listener_check_write_request_data_size(instance, request, item_cnt); + } + + return valid; +} + +bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm) { + furi_assert(instance); + furi_assert(request_idm); + + const FelicaIDm* idm = &instance->data->idm; + return memcmp(idm->data, request_idm->data, 8) == 0; +} + +void felica_listener_reset(FelicaListener* instance) { + furi_assert(instance); + + instance->auth.context.auth_status.internal = false; + instance->auth.context.auth_status.external = false; + instance->data->data.fs.state.data[0] = 0; + instance->rc_written = false; + memset(instance->auth.session_key.data, 0, FELICA_DATA_BLOCK_SIZE); + + memcpy(instance->data->data.fs.mc.data, instance->mc_shadow.data, FELICA_DATA_BLOCK_SIZE); + + felica_wcnt_post_process(instance->data); +} + +static uint8_t felica_listener_get_block_index(uint8_t number) { + if(number >= FELICA_BLOCK_INDEX_RC && number < FELICA_BLOCK_INDEX_WCNT) { + return number - 0x80 + FELICA_BLOCK_INDEX_REG + 1; + } else if(number >= FELICA_BLOCK_INDEX_WCNT && number <= FELICA_BLOCK_INDEX_STATE) { + return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 1; + } else if(number == FELICA_BLOCK_INDEX_CRC_CHECK) { + return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 2; + } + + return number; +} + +static bool felica_block_exists(uint8_t number) { + return !( + (number > FELICA_BLOCK_INDEX_REG && number < FELICA_BLOCK_INDEX_RC) || + (number > FELICA_BLOCK_INDEX_MC && number < FELICA_BLOCK_INDEX_WCNT) || + (number > FELICA_BLOCK_INDEX_STATE && number < FELICA_BLOCK_INDEX_CRC_CHECK) || + (number > FELICA_BLOCK_INDEX_CRC_CHECK)); +} + +static bool + felica_get_mc_bit(const FelicaListener* instance, uint8_t byte_index, uint8_t bit_number) { + uint8_t* mc = instance->data->data.fs.mc.data; + + uint16_t flags = *((uint16_t*)&mc[byte_index]); + bool bit = (flags & (1 << bit_number)) != 0; + return bit; +} + +static bool felica_block_requires_auth( + const FelicaListener* instance, + uint8_t command, + uint8_t block_number) { + bool need_auth = false; + if(block_number <= FELICA_BLOCK_INDEX_REG) { + uint8_t mc_flag_index = command; + need_auth = felica_get_mc_bit(instance, mc_flag_index, block_number); + } + return need_auth; +} + +static bool felica_block_is_readonly(const FelicaListener* instance, uint8_t block_number) { + bool readonly = false; + if(block_number <= FELICA_BLOCK_INDEX_REG) { + readonly = !felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, block_number); + } else if( + ((block_number == FELICA_BLOCK_INDEX_ID) || (block_number == FELICA_BLOCK_INDEX_SER_C)) || + (block_number >= FELICA_BLOCK_INDEX_CKV && block_number <= FELICA_BLOCK_INDEX_CK)) { + const FelicaData* data = instance->data; + readonly = FELICA_SYSTEM_BLOCK_RO_ACCESS(data); + } else if( + (block_number == FELICA_BLOCK_INDEX_MAC) || (block_number == FELICA_BLOCK_INDEX_WCNT) || + (block_number == FELICA_BLOCK_INDEX_D_ID) || + (block_number == FELICA_BLOCK_INDEX_CRC_CHECK)) { + readonly = true; + } + return readonly; +} + +static bool felica_block_requires_mac(const FelicaListener* instance, uint8_t block_number) { + bool need_mac = false; + if(block_number <= FELICA_BLOCK_INDEX_REG) { + need_mac = felica_get_mc_bit(instance, FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11, block_number); + } else if(block_number == FELICA_BLOCK_INDEX_CK || block_number == FELICA_BLOCK_INDEX_CKV) { + need_mac = felica_get_mc_bit(instance, FELICA_MC_CKCKV_W_MAC_A, 0); + } else if(block_number == FELICA_BLOCK_INDEX_STATE) { + need_mac = felica_get_mc_bit(instance, FELICA_MC_STATE_W_MAC_A, 0); + } + return need_mac; +} + +static void felica_handler_read_block( + FelicaListener* instance, + const uint8_t block_number, + const uint8_t resp_data_index, + FelicaListenerReadCommandResponse* response) { + uint8_t num = felica_listener_get_block_index(block_number); + memcpy( + &response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE], + &instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2], + FELICA_DATA_BLOCK_SIZE); + response->header.length += FELICA_DATA_BLOCK_SIZE; +} + +static void felica_handler_read_all_zeros( + FelicaListener* instance, + const uint8_t block_number, + const uint8_t resp_data_index, + FelicaListenerReadCommandResponse* response) { + UNUSED(instance); + UNUSED(block_number); + + memset(&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE], 0, FELICA_DATA_BLOCK_SIZE); + response->header.length += FELICA_DATA_BLOCK_SIZE; +} + +static void felica_handler_read_mac_a_block( + FelicaListener* instance, + const uint8_t block_number, + const uint8_t resp_data_index, + FelicaListenerReadCommandResponse* response) { + if((resp_data_index != response->block_count - 1) || (resp_data_index == 0)) { + felica_handler_read_all_zeros(instance, block_number, resp_data_index, response); + instance->mac_calc_start = resp_data_index + 1; + } else { + felica_calculate_mac_read( + &instance->auth.des_context, + instance->auth.session_key.data, + instance->data->data.fs.rc.data, + &instance->requested_blocks[instance->mac_calc_start], + response->block_count - instance->mac_calc_start, + &response->data[instance->mac_calc_start * FELICA_DATA_BLOCK_SIZE], + instance->data->data.fs.mac_a.data); + felica_handler_read_block(instance, block_number, resp_data_index, response); + } +} + +FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number) { + FelicaCommanReadBlockHandler handler = felica_handler_read_block; + + if(block_number == FELICA_BLOCK_INDEX_RC || block_number == FELICA_BLOCK_INDEX_CK) { + handler = felica_handler_read_all_zeros; + } else if(block_number == FELICA_BLOCK_INDEX_MAC_A) { + handler = felica_handler_read_mac_a_block; + } + + return handler; +} + +static bool felica_validate_read_block_list( + FelicaListener* instance, + const FelicaListenerReadRequest* const request, + FelicaCommandResponseHeader* response) { + uint8_t mac_a_pos = 0; + bool mac_a_present = false, mac_present = false; + + const FelicaBlockListElement* item = + felica_listener_block_list_item_get_first(instance, request); + for(uint8_t i = 0; i < request->base.header.block_count; i++) { + if(item->service_code != 0) { + response->SF1 = (1 << i); + response->SF2 = 0xA3; + return false; + } else if(item->access_mode != 0) { + response->SF1 = (1 << i); + response->SF2 = 0xA7; + return false; + } else if( + !felica_listener_block_list_item_validate_block_number(item) || + !felica_block_exists(item->block_number) || + (felica_block_is_readonly(instance, item->block_number) && + request->base.header.service_code != FELICA_SERVICE_RO_ACCESS)) { + response->SF1 = (1 << i); + response->SF2 = 0xA8; + return false; + } else if(item->block_number == FELICA_BLOCK_INDEX_MAC) { + mac_present = true; + } else if(item->block_number == FELICA_BLOCK_INDEX_MAC_A) { + if(!instance->rc_written) { + response->SF1 = (1 << i); + response->SF2 = 0xB2; + return false; + } + if(!mac_a_present) { + mac_a_present = true; + mac_a_pos = i; + } + } else if( + felica_block_requires_auth(instance, request->base.header.code, item->block_number) && + !instance->auth.context.auth_status.external) { + response->SF1 = (1 << i); + response->SF2 = 0xB1; + return false; + } + + if(mac_a_present && mac_present) { + response->SF1 = (1 << mac_a_pos); + response->SF2 = 0xB0; + return false; + } + + item = felica_listener_block_list_item_get_next(instance, item); + } + return true; +} + +bool felica_listener_validate_read_request_and_set_sf( + FelicaListener* instance, + const FelicaListenerReadRequest* const request, + FelicaCommandResponseHeader* resp_header) { + furi_assert(instance); + furi_assert(request); + furi_assert(resp_header); + + bool valid = false; + do { + if(request->base.header.service_num != 0x01) { + resp_header->SF1 = 0xFF; + resp_header->SF2 = 0xA1; + break; + } + if((request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) && + (request->base.header.block_count < FELICA_LISTENER_READ_BLOCK_COUNT_MIN || + request->base.header.block_count > FELICA_LISTENER_READ_BLOCK_COUNT_MAX)) { + resp_header->SF1 = 0xFF; + resp_header->SF2 = 0xA2; + break; + } + + if(request->base.header.service_code != FELICA_SERVICE_RO_ACCESS && + request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) { + resp_header->SF1 = 0x01; + resp_header->SF2 = 0xA6; + break; + } + + if(!felica_validate_read_block_list(instance, request, resp_header)) break; + + resp_header->SF1 = 0x00; + resp_header->SF2 = 0x00; + valid = true; + } while(false); + + return valid; +} + +static bool felica_validate_write_block_list( + FelicaListener* instance, + const FelicaListenerWriteRequest* const request, + const FelicaListenerWriteBlockData* const data, + FelicaListenerWriteCommandResponse* response) { + const FelicaBlockListElement* items[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX] = {}; + items[0] = felica_listener_block_list_item_get_first(instance, request); + items[1] = felica_listener_block_list_item_get_next(instance, items[0]); + + bool write_with_mac = false; + if(request->base.header.block_count == FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX && + items[1]->block_number == FELICA_BLOCK_INDEX_MAC_A) { + write_with_mac = true; + } else if( + request->base.header.block_count < FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN || + request->base.header.block_count > FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX) { + response->SF1 = 0x02; + response->SF2 = 0xB2; + return false; + } + + for(uint8_t i = 0; i < request->base.header.block_count; i++) { + const FelicaBlockListElement* item = items[i]; + if(!felica_listener_block_list_item_validate_block_number(item) || + !felica_block_exists(item->block_number) || + ((i == 1) && (item->block_number != FELICA_BLOCK_INDEX_MAC_A))) { + response->SF1 = (1 << i); + response->SF2 = 0xA8; + return false; + } + + if(felica_block_is_readonly(instance, item->block_number) || + (felica_block_requires_mac(instance, item->block_number) && !write_with_mac) || + ((i == 0) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A))) { + response->SF1 = 0x01; + response->SF2 = 0xA8; + return false; + } + + if(item->service_code != 0) { + response->SF1 = (1 << i); + response->SF2 = 0xA3; + return false; + } else if(item->access_mode != 0) { + response->SF1 = (1 << i); + response->SF2 = 0xA7; + return false; + } else if((i == 1) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A)) { + uint8_t calculated_mac[8]; + felica_calculate_mac_write( + &instance->auth.des_context, + instance->auth.session_key.data, + instance->data->data.fs.rc.data, + data->blocks[1].data + 8, + data->blocks[0].data, + calculated_mac); + + if(!instance->rc_written || (memcmp(calculated_mac, data->blocks[1].data, 8) != 0) || + !felica_wcnt_check_error_boundary(instance->data)) { + response->SF1 = 0x02; + response->SF2 = 0xB2; + return false; + } + } else if( + felica_block_requires_auth(instance, request->base.header.code, item->block_number) && + !instance->auth.context.auth_status.external) { + response->SF1 = 0x01; + response->SF2 = 0xB1; + return false; + } + } + return true; +} + +bool felica_listener_validate_write_request_and_set_sf( + FelicaListener* instance, + const FelicaListenerWriteRequest* const request, + const FelicaListenerWriteBlockData* const data, + FelicaListenerWriteCommandResponse* response) { + furi_assert(instance); + furi_assert(request); + furi_assert(data); + furi_assert(response); + + bool valid = false; + do { + if(request->base.header.service_num != 0x01) { + response->SF1 = 0xFF; + response->SF2 = 0xA1; + break; + } + + if((request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) && + (request->base.header.block_count < 0x01 || request->base.header.block_count > 0x02)) { + response->SF1 = 0xFF; + response->SF2 = 0xA2; + break; + } + + if(request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) { + response->SF1 = 0x01; + response->SF2 = 0xA6; + break; + } + + if(!felica_validate_write_block_list(instance, request, data, response)) break; + + if(felica_wcnt_check_warning_boundary(instance->data)) { + response->SF1 = 0xFF; + response->SF2 = 0x71; + } else { + response->SF1 = 0x00; + response->SF2 = 0x00; + } + valid = true; + } while(false); + return valid; +} + +static void felica_handler_write_block( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block) { + uint8_t num = felica_listener_get_block_index(block_number); + + memcpy( + &instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2], + data_block->data, + FELICA_DATA_BLOCK_SIZE); +} + +static void felica_handler_write_rc_block( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block) { + felica_handler_write_block(instance, block_number, data_block); + + felica_calculate_session_key( + &instance->auth.des_context, + instance->data->data.fs.ck.data, + instance->data->data.fs.rc.data, + instance->auth.session_key.data); + instance->rc_written = true; +} + +static void felica_handler_write_reg_block( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block) { + UNUSED(block_number); + + typedef struct { + uint32_t A; + uint32_t B; + uint64_t C; + } FELICA_REG_BLOCK; + + FELICA_REG_BLOCK* Reg = (FELICA_REG_BLOCK*)instance->data->data.fs.reg.data; + FELICA_REG_BLOCK* RegNew = (FELICA_REG_BLOCK*)data_block->data; + + if(Reg->A >= RegNew->A) { + Reg->A = RegNew->A; + } + + if(Reg->B >= RegNew->B) { + Reg->B = RegNew->B; + } + + if((Reg->A >= RegNew->A) && (Reg->B >= RegNew->B)) { + Reg->C = RegNew->C; + } +} + +static void felica_handler_write_mc_block( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block) { + UNUSED(block_number); + + bool mc_bits_permission = felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, 15); + const FelicaData* data = instance->data; + bool mc_system_block_permission = FELICA_SYSTEM_BLOCK_RW_ACCESS(data); + for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) { + if((i <= 1) && mc_bits_permission) { + instance->mc_shadow.data[i] &= data_block->data[i]; + } else if( + (i >= FELICA_MC_ALL_BYTE && i <= FELICA_MC_CKCKV_W_MAC_A) && + (mc_system_block_permission)) { + instance->mc_shadow.data[i] = data_block->data[i]; + } else if( + (i >= FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 && i <= FELICA_MC_STATE_W_MAC_A) && + (mc_bits_permission)) { + instance->mc_shadow.data[i] |= data_block->data[i]; + } else if(i >= FELICA_MC_RESERVED_13) { + instance->mc_shadow.data[i] = 0; + } + } +} + +static void felica_handler_write_state_block( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block) { + felica_handler_write_block(instance, block_number, data_block); + bool state = instance->data->data.fs.state.data[0] == 0x01; + instance->auth.context.auth_status.external = state; + instance->auth.context.auth_status.internal = state; +} + +static void felica_handler_write_id_block( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block) { + UNUSED(block_number); + memcpy(&instance->data->data.fs.id.data[8], &data_block->data[8], 8); +} + +FelicaCommandWriteBlockHandler + felica_listener_get_write_block_handler(const uint8_t block_number) { + FelicaCommandWriteBlockHandler handler = felica_handler_write_block; + switch(block_number) { + case FELICA_BLOCK_INDEX_RC: + handler = felica_handler_write_rc_block; + break; + case FELICA_BLOCK_INDEX_REG: + handler = felica_handler_write_reg_block; + break; + case FELICA_BLOCK_INDEX_MC: + handler = felica_handler_write_mc_block; + break; + case FELICA_BLOCK_INDEX_STATE: + handler = felica_handler_write_state_block; + break; + case FELICA_BLOCK_INDEX_ID: + handler = felica_handler_write_id_block; + break; + default: + handler = felica_handler_write_block; + break; + } + return handler; +} + +static FelicaError felica_listener_process_error(NfcError error) { + switch(error) { + case NfcErrorNone: + return FelicaErrorNone; + case NfcErrorTimeout: + return FelicaErrorTimeout; + default: + return FelicaErrorNotPresent; + } +} + +FelicaError + felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer) { + furi_assert(instance); + furi_assert(tx_buffer); + + const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer); + furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE); + + felica_crc_append(instance->tx_buffer); + + FelicaError ret = FelicaErrorNone; + + do { + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + if(error != NfcErrorNone) { + ret = felica_listener_process_error(error); + break; + } + } while(false); + + return ret; +} \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_listener_i.h b/lib/nfc/protocols/felica/felica_listener_i.h index 4fa25a162..43c03a608 100644 --- a/lib/nfc/protocols/felica/felica_listener_i.h +++ b/lib/nfc/protocols/felica/felica_listener_i.h @@ -2,15 +2,72 @@ #include +#define FELICA_LISTENER_READ_BLOCK_COUNT_MAX (4U) +#define FELICA_LISTENER_READ_BLOCK_COUNT_MIN (1U) +#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U) +#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U) + typedef enum { Felica_ListenerStateIdle, Felica_ListenerStateActivated, } FelicaListenerState; +/** Generic Felica request same for both read and write operations. */ +typedef struct { + uint8_t length; + FelicaCommandHeader header; +} FelicaListenerGenericRequest; + +/** Generic request but with list of requested elements. */ +typedef struct { + FelicaListenerGenericRequest base; + FelicaBlockListElement list[]; +} FelicaListenerRequest; + +typedef FelicaListenerRequest FelicaListenerReadRequest; +typedef FelicaListenerRequest FelicaListenerWriteRequest; + +/** Struct for write request data. + * + * All data are placed right after @ref FelicaListenerRequest data. + */ +typedef struct { + FelicaBlockData blocks[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX]; +} FelicaListenerWriteBlockData; + +/** Write command handler signature. + * + * Depending on requested block write behaviour can be different, therefore + * different handlers are used. + */ +typedef void (*FelicaCommandWriteBlockHandler)( + FelicaListener* instance, + const uint8_t block_number, + const FelicaBlockData* data_block); + +/** Read command handler signature. + * + * Depending on requested block read behaviour can be different, therefore + * different handlers are used. + */ +typedef void (*FelicaCommanReadBlockHandler)( + FelicaListener* instance, + const uint8_t block_number, + const uint8_t resp_data_index, + FelicaListenerReadCommandResponse* response); + struct FelicaListener { Nfc* nfc; FelicaData* data; FelicaListenerState state; + FelicaAuthentication auth; + FelicaBlockData mc_shadow; + + uint8_t request_size_buf; + uint8_t block_list_size; + uint8_t requested_blocks[FELICA_LISTENER_READ_BLOCK_COUNT_MAX]; + uint8_t mac_calc_start; + bool rc_written; BitBuffer* tx_buffer; BitBuffer* rx_buffer; @@ -18,4 +75,170 @@ struct FelicaListener { NfcGenericEvent generic_event; NfcGenericCallback callback; void* context; -}; \ No newline at end of file +}; + +/** Resets card authentication state after field off, also resets session key and + * perform post process operations with some blocks. + * + * @param instance pointer to the listener instance to be used. + */ +void felica_listener_reset(FelicaListener* instance); + +/** Performs WCNT increasing in case of write operation. + * + * @param data pointer to Felica card data. + */ +void felica_wcnt_increment(FelicaData* data); + +/** Compares IDm of card loaded for emulation with IDm from request. + * + * @param instance pointer to the listener instance to be used. + * @param request_idm pointer to IDm block from request structure. + * + * @return True if IDms' are equal, otherwise false. + */ +bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm); + +/** This is the first request validation function. + * + * Its main aim is to check whether the input request is valid in general by + * counting the amount of data in request. Function iterates through block list + * elements and data, counts real amount of elements and real amount of + * coresponding elements data (in case of write operation). If block list + * element amount is invalid or request data is missing, such request will be + * ignored. + * + * @param instance pointer to the listener instance to be used. + * @param request pointer to received request. + * + * @return True if block element list contains valid amount of data, + * otherwise false. + */ +bool felica_listener_check_block_list_size( + FelicaListener* instance, + FelicaListenerGenericRequest* request); + +/** Used to take first element from block element list. + * + * Each element in block list can be 2 or 3-bytes length and they can be mixed + * together. Therefore before returning the element, function checks whether + * there is enough bytes in the request for this element, in order to avoid + * memory casting behind the request block. + * + * @param instance pointer to the listener instance to be used. + * @param request pointer to received request. + * + * @return Pointer to the first element of the list or NULL if there is not + * enough bytes in the request. + */ +const FelicaBlockListElement* felica_listener_block_list_item_get_first( + FelicaListener* instance, + const FelicaListenerRequest* request); + +/** Used to take next element from block element list. + * + * It uses pointer to the previous element, takes its length and calculates + * pointer to the next element if there is enough bytes left. + * + * @param instance pointer to the listener instance to be used. + * @param prev_item pointer to the previous item taken. + * + * @return Pointer to the next element of the list or NULL if there is not + * enough bytes in the request. + */ +const FelicaBlockListElement* felica_listener_block_list_item_get_next( + FelicaListener* instance, + const FelicaBlockListElement* prev_item); + +/** Calculates pointer to data blocks in case of write operation, because block + * list elements size can vary. + * + * For calculation it uses previously calculated block list size and + * FelicaListenerGenericRequest size. + * + * @param instance pointer to the listener instance to be used. + * @param generic_request pointer to received request. + * + * @return Pointer to data blocks for write operation. + */ +const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer( + const FelicaListener* const instance, + const FelicaListenerGenericRequest* const generic_request); + +/** Function validates write request data and sets Felica SF1 and SF2 flags + * directly to the response. + * + * When something is wrong with data in the request (for example non-existing + * block is requested) this function sets proper SF1 and SF2 flags which will be + * then send to reader. When every thing is fine SF1 and SF2 are 0. Also this + * function performs authentiocation checks by MAC_A calculation if request + * contains MAC_A. + * + * @param instance pointer to the listener instance to be used. + * @param request pointer to received write request. + * @param data pointer to data which request wants to write. + * @param response pointer to the response to where SF1 and SF2 flags will + * be populated. + * + * @return True if request is fine also SF1 and SF2 will be 0. False if + * something is wrong and SF1, SF2 will provide more detailed + * information about the error. + */ +bool felica_listener_validate_write_request_and_set_sf( + FelicaListener* instance, + const FelicaListenerWriteRequest* const request, + const FelicaListenerWriteBlockData* const data, + FelicaListenerWriteCommandResponse* response); + +/** Function validates read request data and sets Felica SF1 and SF2 flags + * directly to the response. + * + * When something is wrong with data in the request (for example non-existing + * block is requested) this function sets proper SF1 and SF2 flags which will be + * then send to reader. In such case there will be no any data in the request, + * only flags. In case when request is fine SF1 and SF2 will be 0 and data will + * be populated to the response after them. Attention! This function doesn't + * populate any data, it only allows further processing where those data will be + * populated. + * + * @param instance pointer to the listener instance to be used. + * @param request pointer to received write request. + * @param resp_header pointer to the response to where SF1 and SF2 and + * data flags will be populated. + * + * @return True if request is fine also SF1 and SF2 will be 0. False if + * something is wrong and SF1, SF2 will provide more detailed + * information about the error. + */ +bool felica_listener_validate_read_request_and_set_sf( + FelicaListener* instance, + const FelicaListenerReadRequest* const request, + FelicaCommandResponseHeader* resp_header); + +/** Function returns appropiate block handler for processing read operation + * depending on number of block. + * + * @param block_number number of block. + * + * @return pointer to block handler function. + */ +FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number); + +/** Function returns appropiate block handler for processing write operation + * depending on number of block. + * + * @param block_number number of block. + * + * @return pointer to block handler function. + */ +FelicaCommandWriteBlockHandler felica_listener_get_write_block_handler(const uint8_t block_number); + +/** Sends response data from buffer to reader. + * + * @param instance pointer to the listener instance to be used. + * @param tx_buffer buffer with response data. + * + * @return error code or FelicaErrorNone. + */ +FelicaError + felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer); diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index d4366e767..b386f4b4b 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -24,22 +24,6 @@ typedef enum { FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */ } FelicaPollerEventType; -/** - * @brief Stucture for holding Felica session key which is calculated from rc and ck. -*/ -typedef struct { - uint8_t data[FELICA_DATA_BLOCK_SIZE]; -} FelicaSessionKey; - -/** - * @brief Structure used to hold authentication related fields. -*/ -typedef struct { - mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */ - FelicaSessionKey session_key; /**< Calculated session key. */ - FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */ -} FelicaAuthentication; - /** * @brief Felica poller event data. */ diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index f7726be32..8ec4b2889 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -3,11 +3,6 @@ #include #define TAG "FelicaPoller" -#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U) -#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U) - -#define FELICA_SERVICE_RW_ACCESS (0x0009U) -#define FELICA_SERVICE_RO_ACCESS (0x000BU) static FelicaError felica_poller_process_error(NfcError error) { switch(error) { diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index f7df4c845..9857d96d6 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -55,41 +55,6 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; -typedef struct { - uint8_t service_code : 4; - uint8_t access_mode : 3; - uint8_t length : 1; - uint8_t block_number; -} FelicaBlockListElement; - -#pragma pack(push, 1) -typedef struct { - uint8_t code; - FelicaIDm idm; - uint8_t service_num; - uint16_t service_code; - uint8_t block_count; -} FelicaCommandHeader; -#pragma pack(pop) - -typedef struct { - uint8_t length; - uint8_t response_code; - FelicaIDm idm; - uint8_t SF1; - uint8_t SF2; - uint8_t block_count; - uint8_t data[]; -} FelicaPollerReadCommandResponse; - -typedef struct { - uint8_t length; - uint8_t response_code; - FelicaIDm idm; - uint8_t SF1; - uint8_t SF2; -} FelicaPollerWriteCommandResponse; - const FelicaData* felica_poller_get_data(FelicaPoller* instance); /** diff --git a/lib/nfc/protocols/felica/felica_poller_sync.c b/lib/nfc/protocols/felica/felica_poller_sync.c new file mode 100644 index 000000000..f20ff08e1 --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_sync.c @@ -0,0 +1,70 @@ +#include "felica_poller_sync.h" + +#include "felica_poller_i.h" +#include + +#include + +#define FELICA_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0) + +typedef struct { + FelicaAuthenticationContext auth_ctx; + FuriThreadId thread_id; + FelicaError error; + FelicaData data; +} Felica_PollerContext; + +NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolFelica); + + Felica_PollerContext* 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); + } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { + felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth; + memcpy( + felica_event->data->auth_context->card_key.data, + poller_context->auth_ctx.card_key.data, + FELICA_DATA_BLOCK_SIZE); + } + + furi_thread_flags_set(poller_context->thread_id, FELICA_POLLER_FLAG_COMMAND_COMPLETE); + + return NfcCommandStop; +} + +FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key) { + furi_check(nfc); + furi_check(data); + + Felica_PollerContext poller_context = {}; + if(card_key == NULL) { + poller_context.auth_ctx.skip_auth = true; + } else { + poller_context.auth_ctx.skip_auth = false; + memcpy(poller_context.auth_ctx.card_key.data, card_key->data, FELICA_DATA_BLOCK_SIZE); + } + + poller_context.thread_id = furi_thread_get_current_id(); + 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); + furi_thread_flags_clear(FELICA_POLLER_FLAG_COMMAND_COMPLETE); + + nfc_poller_stop(poller); + nfc_poller_free(poller); + + if(poller_context.error == FelicaErrorNone) { + *data = poller_context.data; + } + + return poller_context.error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica_poller_sync.h b/lib/nfc/protocols/felica/felica_poller_sync.h new file mode 100644 index 000000000..fca5a0bfc --- /dev/null +++ b/lib/nfc/protocols/felica/felica_poller_sync.h @@ -0,0 +1,14 @@ +#pragma once + +#include "felica.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 639f2eaec..25084aaa0 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -224,6 +224,11 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, FuriString* full_extracted_fname; if(header->type == MTAR_TDIR) { + // Skip "/" entry since concat would leave it dangling, also want caller to mkdir destination + if(strcmp(header->name, "/") == 0) { + return 0; + } + full_extracted_fname = furi_string_alloc(); path_concat(op_params->work_dir, header->name, full_extracted_fname); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b8f77266c..91999f7a0 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,64.0,, +Version,+,64.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e0d592d9c..61d4fd43d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,64.0,, +Version,+,64.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -138,7 +138,9 @@ Header,+,lib/nfc/nfc_scanner.h,, Header,+,lib/nfc/protocols/emv/emv.h,, Header,+,lib/nfc/protocols/emv/emv_poller.h,, Header,+,lib/nfc/protocols/felica/felica.h,, +Header,+,lib/nfc/protocols/felica/felica_listener.h,, Header,+,lib/nfc/protocols/felica/felica_poller.h,, +Header,+,lib/nfc/protocols/felica/felica_poller_sync.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, @@ -1038,6 +1040,7 @@ Function,-,fdimf,float,"float, float" Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" Function,+,felica_alloc,FelicaData*, +Function,+,felica_calculate_mac_read,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, const uint8_t*, uint8_t*" Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" @@ -1049,6 +1052,7 @@ Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*" Function,+,felica_reset,void,FelicaData* Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" diff --git a/targets/f7/furi_hal/furi_hal_nfc_event.c b/targets/f7/furi_hal/furi_hal_nfc_event.c index e434e6a35..9bcd2f1fe 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_event.c +++ b/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -77,6 +77,9 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { if(irq & ST25R3916_IRQ_MASK_WU_A_X) { event |= FuriHalNfcEventListenerActive; } + if(irq & ST25R3916_IRQ_MASK_WU_F) { + event |= FuriHalNfcEventListenerActive; + } } if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) { event |= FuriHalNfcEventTimerFwtExpired; diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index 82239fbc1..89305877e 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -55,6 +55,7 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) { furi_assert(handle); + st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); st25r3916_write_reg( handle, ST25R3916_REG_OP_CONTROL, @@ -89,8 +90,8 @@ static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* ha st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00); // No gain reduction on AM and PM channels st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00); - // 10% ASK modulation - st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent); + // 40% ASK modulation + st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_40percent); // Correlator setup st25r3916_write_reg( @@ -142,9 +143,7 @@ FuriHalNfcError furi_hal_nfc_felica_listener_tx( FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { - UNUSED(handle); - UNUSED(tx_data); - UNUSED(tx_bits); + furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits); return FuriHalNfcErrorNone; }