diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam index 22309425..ffc4b518 100644 --- a/applications/picopass/application.fam +++ b/applications/picopass/application.fam @@ -5,7 +5,7 @@ App( entry_point="picopass_app", cdefines=["APP_PICOPASS"], requires=["storage", "gui"], - stack_size=1 * 1024, + stack_size=4 * 1024, icon="A_Plugins_14", order=30, ) diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index 8c0db4e2..e9f48b67 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -56,6 +56,11 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); + // Loading + picopass->loading = loading_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewLoading, loading_get_view(picopass->loading)); + // Text Input picopass->text_input = text_input_alloc(); view_dispatcher_add_view( @@ -86,6 +91,10 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); popup_free(picopass->popup); + // Loading + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading); + loading_free(picopass->loading); + // TextInput view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); text_input_free(picopass->text_input); @@ -148,6 +157,20 @@ void picopass_blink_stop(Picopass* picopass) { notification_message(picopass->notifications, &picopass_sequence_blink_stop); } +void picopass_show_loading_popup(void* context, bool show) { + Picopass* picopass = context; + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + + if(show) { + // Raise timer priority so that animations can play + vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoading); + } else { + // Restore default timer priority + vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); + } +} + int32_t picopass_app(void* p) { UNUSED(p); Picopass* picopass = picopass_alloc(); diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c index 9b422edd..4cd6faaa 100644 --- a/applications/picopass/picopass_device.c +++ b/applications/picopass/picopass_device.c @@ -8,6 +8,9 @@ static const char* picopass_file_header = "Flipper Picopass device"; static const uint32_t picopass_file_version = 1; +const uint8_t picopass_iclass_decryptionkey[] = + {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; + PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); picopass_dev->dev_data.pacs.legacy = false; @@ -15,6 +18,7 @@ PicopassDevice* picopass_device_alloc() { picopass_dev->dev_data.pacs.pin_length = 0; picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); + string_init(picopass_dev->load_path); return picopass_dev; } @@ -111,7 +115,7 @@ static bool picopass_device_save_file( } while(0); if(!saved) { - dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile"); } string_clear(temp_str); flipper_format_free(file); @@ -128,11 +132,83 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { return false; } +static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool show_dialog) { + bool parsed = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + PicopassBlock* AA1 = dev->dev_data.AA1; + PicopassPacs* pacs = &dev->dev_data.pacs; + string_t temp_str; + string_init(temp_str); + bool deprecated_version = false; + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, true); + } + + do { + if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; + + // Read and verify file header + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(string_cmp_str(temp_str, picopass_file_header) || (version != picopass_file_version)) { + deprecated_version = true; + break; + } + + // Parse header blocks + bool block_read = true; + for(size_t i = 0; i < 6; i++) { + string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + block_read = false; + break; + } + } + + size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; + for(size_t i = 6; i < app_limit; i++) { + string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { + block_read = false; + break; + } + } + if(!block_read) break; + + if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break; + if(picopass_device_parse_wiegand(pacs->credential, &pacs->record) != ERR_NONE) break; + + parsed = true; + } while(false); + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, false); + } + + if((!parsed) && (show_dialog)) { + if(deprecated_version) { + dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); + } else { + dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); + } + } + + string_clear(temp_str); + flipper_format_free(file); + + return parsed; +} + void picopass_device_clear(PicopassDevice* dev) { furi_assert(dev); picopass_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); + dev->format = PicopassDeviceSaveFormatHF; + string_reset(dev->load_path); } void picopass_device_free(PicopassDevice* picopass_dev) { @@ -144,6 +220,36 @@ void picopass_device_free(PicopassDevice* picopass_dev) { free(picopass_dev); } +bool picopass_file_select(PicopassDevice* dev) { + furi_assert(dev); + + // Input events and views are managed by file_browser + string_t picopass_app_folder; + string_init_set_str(picopass_app_folder, PICOPASS_APP_FOLDER); + bool res = dialog_file_browser_show( + dev->dialogs, + dev->load_path, + picopass_app_folder, + PICOPASS_APP_EXTENSION, + true, + &I_Nfc_10px, + true); + string_clear(picopass_app_folder); + if(res) { + string_t filename; + string_init(filename); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN); + res = picopass_device_load_data(dev, dev->load_path, true); + if(res) { + picopass_device_set_name(dev, dev->dev_name); + } + string_clear(filename); + } + + return res; +} + void picopass_device_data_clear(PicopassDeviceData* dev_data) { for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data)); @@ -152,3 +258,122 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { dev_data->pacs.se_enabled = false; dev_data->pacs.pin_length = 0; } + +bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { + furi_assert(dev); + + bool deleted = false; + string_t file_path; + string_init(file_path); + + do { + // Delete original file + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(file_path, dev->load_path); + } else { + string_printf( + file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION); + } + if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; + deleted = true; + } while(0); + + if(!deleted) { + dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + } + + string_clear(file_path); + return deleted; +} + +void picopass_device_set_loading_callback( + PicopassDevice* dev, + PicopassLoadingCallback callback, + void* context) { + furi_assert(dev); + + dev->loading_cb = callback; + dev->loading_cb_ctx = context; +} + +ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) { + uint8_t key[32] = {0}; + memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); + mbedtls_des3_context ctx; + mbedtls_des3_init(&ctx); + mbedtls_des3_set2key_dec(&ctx, key); + mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); + mbedtls_des3_free(&ctx); + return ERR_NONE; +} + +ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { + ReturnCode err; + + // Thank you proxmark! + pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); + pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); + + pacs->biometrics = AA1[6].data[4]; + pacs->pin_length = AA1[6].data[6] & 0x0F; + pacs->encryption = AA1[6].data[7]; + + if(pacs->encryption == PicopassDeviceEncryption3DES) { + FURI_LOG_D(TAG, "3DES Encrypted"); + err = picopass_device_decrypt(AA1[7].data, pacs->credential); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + return err; + } + + err = picopass_device_decrypt(AA1[8].data, pacs->pin0); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + return err; + } + + err = picopass_device_decrypt(AA1[9].data, pacs->pin1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + return err; + } + } else if(pacs->encryption == PicopassDeviceEncryptionNone) { + FURI_LOG_D(TAG, "No Encryption"); + memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); + memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); + memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); + } else if(pacs->encryption == PicopassDeviceEncryptionDES) { + FURI_LOG_D(TAG, "DES Encrypted"); + } else { + FURI_LOG_D(TAG, "Unknown encryption"); + } + + return ERR_NONE; +} + +ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { + uint32_t* halves = (uint32_t*)data; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + record->bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + record->bitLength = 63 - leading0s; + } + FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); + + if(record->bitLength == 26) { + uint8_t* v4 = data + 4; + uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + + record->CardNumber = (bot >> 1) & 0xFFFF; + record->FacilityCode = (bot >> 17) & 0xFF; + FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); + record->valid = true; + } else { + record->CardNumber = 0; + record->FacilityCode = 0; + record->valid = false; + } + return ERR_NONE; +} diff --git a/applications/picopass/picopass_device.h b/applications/picopass/picopass_device.h index 89e031ca..0415b879 100644 --- a/applications/picopass/picopass_device.h +++ b/applications/picopass/picopass_device.h @@ -7,6 +7,10 @@ #include +#include +#include +#include + #define PICOPASS_DEV_NAME_MAX_LEN 22 #define PICOPASS_READER_DATA_MAX_SIZE 64 #define PICOPASS_BLOCK_LEN 8 @@ -20,6 +24,8 @@ #define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" +typedef void (*PicopassLoadingCallback)(void* context, bool state); + typedef enum { PicopassDeviceEncryptionUnknown = 0, PicopassDeviceEncryptionNone = 0x14, @@ -67,6 +73,9 @@ typedef struct { char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; string_t load_path; PicopassDeviceSaveFormat format; + PicopassLoadingCallback loading_cb; + void* loading_cb_ctx; + } PicopassDevice; PicopassDevice* picopass_device_alloc(); @@ -77,6 +86,18 @@ void picopass_device_set_name(PicopassDevice* dev, const char* name); bool picopass_device_save(PicopassDevice* dev, const char* dev_name); +bool picopass_file_select(PicopassDevice* dev); + void picopass_device_data_clear(PicopassDeviceData* dev_data); void picopass_device_clear(PicopassDevice* dev); + +bool picopass_device_delete(PicopassDevice* dev, bool use_load_path); + +void picopass_device_set_loading_callback( + PicopassDevice* dev, + PicopassLoadingCallback callback, + void* context); + +ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); +ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record); diff --git a/applications/picopass/picopass_i.h b/applications/picopass/picopass_i.h index dbf4f8be..d295f53a 100644 --- a/applications/picopass/picopass_i.h +++ b/applications/picopass/picopass_i.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -55,6 +56,7 @@ struct Picopass { // Common Views Submenu* submenu; Popup* popup; + Loading* loading; TextInput* text_input; Widget* widget; }; @@ -62,6 +64,7 @@ struct Picopass { typedef enum { PicopassViewMenu, PicopassViewPopup, + PicopassViewLoading, PicopassViewTextInput, PicopassViewWidget, } PicopassView; @@ -75,3 +78,5 @@ void picopass_text_store_clear(Picopass* picopass); void picopass_blink_start(Picopass* picopass); void picopass_blink_stop(Picopass* picopass); + +void picopass_show_loading_popup(void* context, bool show); diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c index 3079a98c..88df8d45 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/picopass/picopass_worker.c @@ -1,23 +1,9 @@ #include "picopass_worker_i.h" -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include #define TAG "PicopassWorker" const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -const uint8_t picopass_iclass_decryptionkey[] = - {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; +const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; static void picopass_worker_enable_field() { st25r3916TxRxOn(); @@ -31,44 +17,6 @@ static ReturnCode picopass_worker_disable_field(ReturnCode rc) { return rc; } -static ReturnCode picopass_worker_decrypt(uint8_t* enc_data, uint8_t* dec_data) { - uint8_t key[32] = {0}; - memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); - mbedtls_des3_context ctx; - mbedtls_des3_init(&ctx); - mbedtls_des3_set2key_dec(&ctx, key); - mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); - mbedtls_des3_free(&ctx); - return ERR_NONE; -} - -static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { - uint32_t* halves = (uint32_t*)data; - if(halves[0] == 0) { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); - record->bitLength = 31 - leading0s; - } else { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); - record->bitLength = 63 - leading0s; - } - FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); - - if(record->bitLength == 26) { - uint8_t* v4 = data + 4; - uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); - - record->CardNumber = (bot >> 1) & 0xFFFF; - record->FacilityCode = (bot >> 17) & 0xFF; - FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); - record->valid = true; - } else { - record->CardNumber = 0; - record->FacilityCode = 0; - record->valid = false; - } - return ERR_NONE; -} - /***************************** Picopass Worker API *******************************/ PicopassWorker* picopass_worker_alloc() { @@ -272,46 +220,15 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); } - // Thank you proxmark! - pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); - pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); - - pacs->biometrics = AA1[6].data[4]; - pacs->pin_length = AA1[6].data[6] & 0x0F; - pacs->encryption = AA1[6].data[7]; - - if(pacs->encryption == PicopassDeviceEncryption3DES) { - FURI_LOG_D(TAG, "3DES Encrypted"); - err = picopass_worker_decrypt(AA1[7].data, pacs->credential); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - - err = picopass_worker_decrypt(AA1[8].data, pacs->pin0); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - - err = picopass_worker_decrypt(AA1[9].data, pacs->pin1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - } else if(pacs->encryption == PicopassDeviceEncryptionNone) { - FURI_LOG_D(TAG, "No Encryption"); - memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); - memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); - memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); - } else if(pacs->encryption == PicopassDeviceEncryptionDES) { - FURI_LOG_D(TAG, "DES Encrypted"); - } else { - FURI_LOG_D(TAG, "Unknown encryption"); - break; + err = picopass_device_parse_credential(AA1, pacs); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); } - picopass_worker_parse_wiegand(pacs->credential, &pacs->record); + err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); + } // Notify caller and exit if(picopass_worker->callback) { diff --git a/applications/picopass/picopass_worker_i.h b/applications/picopass/picopass_worker_i.h index 2610d5e7..78995190 100644 --- a/applications/picopass/picopass_worker_i.h +++ b/applications/picopass/picopass_worker_i.h @@ -6,6 +6,15 @@ #include #include +#include + +#include +#include +#include +#include + +#include + struct PicopassWorker { FuriThread* thread; Storage* storage; diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h index 0a3e73f2..87745378 100755 --- a/applications/picopass/scenes/picopass_scene_config.h +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -5,3 +5,7 @@ ADD_SCENE(picopass, card_menu, CardMenu) ADD_SCENE(picopass, save_name, SaveName) ADD_SCENE(picopass, save_success, SaveSuccess) ADD_SCENE(picopass, saved_menu, SavedMenu) +ADD_SCENE(picopass, file_select, FileSelect) +ADD_SCENE(picopass, device_info, DeviceInfo) +ADD_SCENE(picopass, delete, Delete) +ADD_SCENE(picopass, delete_success, DeleteSuccess) diff --git a/applications/picopass/scenes/picopass_scene_delete.c b/applications/picopass/scenes/picopass_scene_delete.c new file mode 100644 index 00000000..fb23cb5d --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_delete.c @@ -0,0 +1,58 @@ +#include "../picopass_i.h" + +void picopass_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { + Picopass* picopass = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_delete_on_enter(void* context) { + Picopass* picopass = context; + + // Setup Custom Widget view + char temp_str[64]; + snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", picopass->dev->dev_name); + widget_add_text_box_element( + picopass->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false); + widget_add_button_element( + picopass->widget, + GuiButtonTypeLeft, + "Back", + picopass_scene_delete_widget_callback, + picopass); + widget_add_button_element( + picopass->widget, + GuiButtonTypeRight, + "Delete", + picopass_scene_delete_widget_callback, + picopass); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_delete_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + return scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + if(picopass_device_delete(picopass->dev, true)) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeleteSuccess); + } else { + scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + consumed = true; + } + } + return consumed; +} + +void picopass_scene_delete_on_exit(void* context) { + Picopass* picopass = context; + + widget_reset(picopass->widget); +} diff --git a/applications/picopass/scenes/picopass_scene_delete_success.c b/applications/picopass/scenes/picopass_scene_delete_success.c new file mode 100755 index 00000000..f2a36a7f --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_delete_success.c @@ -0,0 +1,40 @@ +#include "../picopass_i.h" + +void picopass_scene_delete_success_popup_callback(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); +} + +void picopass_scene_delete_success_on_enter(void* context) { + Picopass* picopass = context; + + // Setup view + Popup* popup = picopass->popup; + popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); + popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, picopass); + popup_set_callback(popup, picopass_scene_delete_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); +} + +bool picopass_scene_delete_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + } + return consumed; +} + +void picopass_scene_delete_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + popup_reset(picopass->popup); +} diff --git a/applications/picopass/scenes/picopass_scene_device_info.c b/applications/picopass/scenes/picopass_scene_device_info.c new file mode 100644 index 00000000..38891b67 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_device_info.c @@ -0,0 +1,82 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_device_info_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + Picopass* picopass = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_device_info_on_enter(void* context) { + Picopass* picopass = context; + + string_t credential_str; + string_t wiegand_str; + string_init(credential_str); + string_init(wiegand_str); + + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + // Setup view + PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + Widget* widget = picopass->widget; + + size_t bytesLength = 1 + pacs->record.bitLength / 8; + string_set_str(credential_str, ""); + for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { + string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } + + if(pacs->record.valid) { + string_cat_printf( + wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); + } else { + string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); + } + + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + widget_add_string_element( + widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + + string_clear(credential_str); + string_clear(wiegand_str); + + widget_add_button_element( + picopass->widget, + GuiButtonTypeLeft, + "Back", + picopass_scene_device_info_widget_callback, + picopass); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == PicopassCustomEventViewExit) { + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); + consumed = true; + } + return consumed; +} + +void picopass_scene_device_info_on_exit(void* context) { + Picopass* picopass = context; + + // Clear views + widget_reset(picopass->widget); +} diff --git a/applications/picopass/scenes/picopass_scene_file_select.c b/applications/picopass/scenes/picopass_scene_file_select.c new file mode 100644 index 00000000..b3d4c3d7 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_file_select.c @@ -0,0 +1,25 @@ +#include "../picopass_i.h" +#include "picopass/picopass_device.h" + +void picopass_scene_file_select_on_enter(void* context) { + Picopass* picopass = context; + // Process file_select return + picopass_device_set_loading_callback(picopass->dev, picopass_show_loading_popup, picopass); + if(picopass_file_select(picopass->dev)) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSavedMenu); + } else { + scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + picopass_device_set_loading_callback(picopass->dev, NULL, picopass); +} + +bool picopass_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void picopass_scene_file_select_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/picopass/scenes/picopass_scene_saved_menu.c index 232cf26a..8f0ce40b 100644 --- a/applications/picopass/scenes/picopass_scene_saved_menu.c +++ b/applications/picopass/scenes/picopass_scene_saved_menu.c @@ -1,5 +1,11 @@ #include "../picopass_i.h" +enum SubmenuIndex { + SubmenuIndexDelete, + SubmenuIndexInfo, + SubmenuIndexWrite, +}; + void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { Picopass* picopass = context; @@ -8,6 +14,16 @@ void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { void picopass_scene_saved_menu_on_enter(void* context) { Picopass* picopass = context; + Submenu* submenu = picopass->submenu; + + submenu_add_item( + submenu, + "Delete", + SubmenuIndexDelete, + picopass_scene_saved_menu_submenu_callback, + picopass); + submenu_add_item( + submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass); submenu_set_selected_item( picopass->submenu, @@ -23,6 +39,14 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { scene_manager_set_scene_state( picopass->scene_manager, PicopassSceneSavedMenu, event.event); + + if(event.event == SubmenuIndexDelete) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneDelete); + consumed = true; + } else if(event.event == SubmenuIndexInfo) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeviceInfo); + consumed = true; + } } return consumed; diff --git a/applications/picopass/scenes/picopass_scene_start.c b/applications/picopass/scenes/picopass_scene_start.c index 7f42fb13..76c18a22 100644 --- a/applications/picopass/scenes/picopass_scene_start.c +++ b/applications/picopass/scenes/picopass_scene_start.c @@ -17,6 +17,8 @@ void picopass_scene_start_on_enter(void* context) { Submenu* submenu = picopass->submenu; submenu_add_item( submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); + submenu_add_item( + submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); submenu_set_selected_item( submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); @@ -32,6 +34,9 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); consumed = true; + } else if(event.event == SubmenuIndexSaved) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); + consumed = true; } scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); } diff --git a/lib/ST25RFAL002/include/rfal_picopass.h b/lib/ST25RFAL002/include/rfal_picopass.h index 5b815025..2baf96f3 100644 --- a/lib/ST25RFAL002/include/rfal_picopass.h +++ b/lib/ST25RFAL002/include/rfal_picopass.h @@ -26,6 +26,7 @@ enum { RFAL_PICOPASS_CMD_READCHECK = 0x88, RFAL_PICOPASS_CMD_CHECK = 0x05, RFAL_PICOPASS_CMD_READ = 0x0C, + RFAL_PICOPASS_CMD_WRITE = 0x0C, }; typedef struct { @@ -58,5 +59,6 @@ ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); +ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); #endif /* RFAL_PICOPASS_H */ diff --git a/lib/ST25RFAL002/source/rfal_picopass.c b/lib/ST25RFAL002/source/rfal_picopass.c index 55dbe649..d4422e41 100644 --- a/lib/ST25RFAL002/source/rfal_picopass.c +++ b/lib/ST25RFAL002/source/rfal_picopass.c @@ -158,3 +158,29 @@ ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRe fwt); return ret; } + +ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { + ReturnCode ret; + + uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_WRITE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + memcpy(txBuf + 2, data, RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy(txBuf + 10, mac, 4); + + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + rfalPicoPassReadBlockRes readRes; + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)&readRes, + sizeof(rfalPicoPassReadBlockRes), + &recvLen, + flags, + fwt); + + // TODO: compare response + + return ret; +}