Picopass load/info/delete (#1562)
* increase stack size * rfalPicoPassPollerWriteBlock * UI for loading picopass * Move picopass parsing and add delete, delete success Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									ddd5d5a535
								
							
						
					
					
						commit
						f92127c0a7
					
				| @ -5,7 +5,7 @@ App( | |||||||
|     entry_point="picopass_app", |     entry_point="picopass_app", | ||||||
|     cdefines=["APP_PICOPASS"], |     cdefines=["APP_PICOPASS"], | ||||||
|     requires=["storage", "gui"], |     requires=["storage", "gui"], | ||||||
|     stack_size=1 * 1024, |     stack_size=4 * 1024, | ||||||
|     icon="A_Plugins_14", |     icon="A_Plugins_14", | ||||||
|     order=30, |     order=30, | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -56,6 +56,11 @@ Picopass* picopass_alloc() { | |||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); |         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
 |     // Text Input
 | ||||||
|     picopass->text_input = text_input_alloc(); |     picopass->text_input = text_input_alloc(); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
| @ -86,6 +91,10 @@ void picopass_free(Picopass* picopass) { | |||||||
|     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); |     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); | ||||||
|     popup_free(picopass->popup); |     popup_free(picopass->popup); | ||||||
| 
 | 
 | ||||||
|  |     // Loading
 | ||||||
|  |     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading); | ||||||
|  |     loading_free(picopass->loading); | ||||||
|  | 
 | ||||||
|     // TextInput
 |     // TextInput
 | ||||||
|     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); |     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); | ||||||
|     text_input_free(picopass->text_input); |     text_input_free(picopass->text_input); | ||||||
| @ -148,6 +157,20 @@ void picopass_blink_stop(Picopass* picopass) { | |||||||
|     notification_message(picopass->notifications, &picopass_sequence_blink_stop); |     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) { | int32_t picopass_app(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|     Picopass* picopass = picopass_alloc(); |     Picopass* picopass = picopass_alloc(); | ||||||
|  | |||||||
| @ -8,6 +8,9 @@ | |||||||
| static const char* picopass_file_header = "Flipper Picopass device"; | static const char* picopass_file_header = "Flipper Picopass device"; | ||||||
| static const uint32_t picopass_file_version = 1; | 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_device_alloc() { | ||||||
|     PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); |     PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); | ||||||
|     picopass_dev->dev_data.pacs.legacy = false; |     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->dev_data.pacs.pin_length = 0; | ||||||
|     picopass_dev->storage = furi_record_open(RECORD_STORAGE); |     picopass_dev->storage = furi_record_open(RECORD_STORAGE); | ||||||
|     picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); |     picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|  |     string_init(picopass_dev->load_path); | ||||||
|     return picopass_dev; |     return picopass_dev; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -111,7 +115,7 @@ static bool picopass_device_save_file( | |||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     if(!saved) { |     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); |     string_clear(temp_str); | ||||||
|     flipper_format_free(file); |     flipper_format_free(file); | ||||||
| @ -128,11 +132,83 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { | |||||||
|     return false; |     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) { | void picopass_device_clear(PicopassDevice* dev) { | ||||||
|     furi_assert(dev); |     furi_assert(dev); | ||||||
| 
 | 
 | ||||||
|     picopass_device_data_clear(&dev->dev_data); |     picopass_device_data_clear(&dev->dev_data); | ||||||
|     memset(&dev->dev_data, 0, sizeof(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) { | void picopass_device_free(PicopassDevice* picopass_dev) { | ||||||
| @ -144,6 +220,36 @@ void picopass_device_free(PicopassDevice* picopass_dev) { | |||||||
|     free(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) { | void picopass_device_data_clear(PicopassDeviceData* dev_data) { | ||||||
|     for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { |     for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { | ||||||
|         memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data)); |         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.se_enabled = false; | ||||||
|     dev_data->pacs.pin_length = 0; |     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; | ||||||
|  | } | ||||||
|  | |||||||
| @ -7,6 +7,10 @@ | |||||||
| 
 | 
 | ||||||
| #include <rfal_picopass.h> | #include <rfal_picopass.h> | ||||||
| 
 | 
 | ||||||
|  | #include <mbedtls/des.h> | ||||||
|  | #include <loclass/optimized_ikeys.h> | ||||||
|  | #include <loclass/optimized_cipher.h> | ||||||
|  | 
 | ||||||
| #define PICOPASS_DEV_NAME_MAX_LEN 22 | #define PICOPASS_DEV_NAME_MAX_LEN 22 | ||||||
| #define PICOPASS_READER_DATA_MAX_SIZE 64 | #define PICOPASS_READER_DATA_MAX_SIZE 64 | ||||||
| #define PICOPASS_BLOCK_LEN 8 | #define PICOPASS_BLOCK_LEN 8 | ||||||
| @ -20,6 +24,8 @@ | |||||||
| #define PICOPASS_APP_EXTENSION ".picopass" | #define PICOPASS_APP_EXTENSION ".picopass" | ||||||
| #define PICOPASS_APP_SHADOW_EXTENSION ".pas" | #define PICOPASS_APP_SHADOW_EXTENSION ".pas" | ||||||
| 
 | 
 | ||||||
|  | typedef void (*PicopassLoadingCallback)(void* context, bool state); | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     PicopassDeviceEncryptionUnknown = 0, |     PicopassDeviceEncryptionUnknown = 0, | ||||||
|     PicopassDeviceEncryptionNone = 0x14, |     PicopassDeviceEncryptionNone = 0x14, | ||||||
| @ -67,6 +73,9 @@ typedef struct { | |||||||
|     char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; |     char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; | ||||||
|     string_t load_path; |     string_t load_path; | ||||||
|     PicopassDeviceSaveFormat format; |     PicopassDeviceSaveFormat format; | ||||||
|  |     PicopassLoadingCallback loading_cb; | ||||||
|  |     void* loading_cb_ctx; | ||||||
|  | 
 | ||||||
| } PicopassDevice; | } PicopassDevice; | ||||||
| 
 | 
 | ||||||
| PicopassDevice* picopass_device_alloc(); | 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_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_data_clear(PicopassDeviceData* dev_data); | ||||||
| 
 | 
 | ||||||
| void picopass_device_clear(PicopassDevice* dev); | 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); | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| #include <gui/modules/popup.h> | #include <gui/modules/popup.h> | ||||||
|  | #include <gui/modules/loading.h> | ||||||
| #include <gui/modules/text_input.h> | #include <gui/modules/text_input.h> | ||||||
| #include <gui/modules/widget.h> | #include <gui/modules/widget.h> | ||||||
| 
 | 
 | ||||||
| @ -55,6 +56,7 @@ struct Picopass { | |||||||
|     // Common Views
 |     // Common Views
 | ||||||
|     Submenu* submenu; |     Submenu* submenu; | ||||||
|     Popup* popup; |     Popup* popup; | ||||||
|  |     Loading* loading; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
|     Widget* widget; |     Widget* widget; | ||||||
| }; | }; | ||||||
| @ -62,6 +64,7 @@ struct Picopass { | |||||||
| typedef enum { | typedef enum { | ||||||
|     PicopassViewMenu, |     PicopassViewMenu, | ||||||
|     PicopassViewPopup, |     PicopassViewPopup, | ||||||
|  |     PicopassViewLoading, | ||||||
|     PicopassViewTextInput, |     PicopassViewTextInput, | ||||||
|     PicopassViewWidget, |     PicopassViewWidget, | ||||||
| } PicopassView; | } PicopassView; | ||||||
| @ -75,3 +78,5 @@ void picopass_text_store_clear(Picopass* picopass); | |||||||
| void picopass_blink_start(Picopass* picopass); | void picopass_blink_start(Picopass* picopass); | ||||||
| 
 | 
 | ||||||
| void picopass_blink_stop(Picopass* picopass); | void picopass_blink_stop(Picopass* picopass); | ||||||
|  | 
 | ||||||
|  | void picopass_show_loading_popup(void* context, bool show); | ||||||
|  | |||||||
| @ -1,23 +1,9 @@ | |||||||
| #include "picopass_worker_i.h" | #include "picopass_worker_i.h" | ||||||
| #include <furi_hal.h> |  | ||||||
| 
 |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <st25r3916.h> |  | ||||||
| #include <rfal_analogConfig.h> |  | ||||||
| #include <rfal_rf.h> |  | ||||||
| #include <rfal_nfc.h> |  | ||||||
| 
 |  | ||||||
| #include <mbedtls/des.h> |  | ||||||
| #include <loclass/optimized_ikeys.h> |  | ||||||
| #include <loclass/optimized_cipher.h> |  | ||||||
| 
 |  | ||||||
| #include <platform.h> |  | ||||||
| 
 | 
 | ||||||
| #define TAG "PicopassWorker" | #define TAG "PicopassWorker" | ||||||
| 
 | 
 | ||||||
| const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | ||||||
| const uint8_t picopass_iclass_decryptionkey[] = | const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | ||||||
|     {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; |  | ||||||
| 
 | 
 | ||||||
| static void picopass_worker_enable_field() { | static void picopass_worker_enable_field() { | ||||||
|     st25r3916TxRxOn(); |     st25r3916TxRxOn(); | ||||||
| @ -31,44 +17,6 @@ static ReturnCode picopass_worker_disable_field(ReturnCode rc) { | |||||||
|     return 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 *******************************/ | /***************************** Picopass Worker API *******************************/ | ||||||
| 
 | 
 | ||||||
| PicopassWorker* picopass_worker_alloc() { | PicopassWorker* picopass_worker_alloc() { | ||||||
| @ -272,47 +220,16 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { | |||||||
|                 FURI_LOG_E(TAG, "picopass_read_card error %d", err); |                 FURI_LOG_E(TAG, "picopass_read_card error %d", err); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Thank you proxmark!
 |             err = picopass_device_parse_credential(AA1, pacs); | ||||||
|             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) { |             if(err != ERR_NONE) { | ||||||
|                     FURI_LOG_E(TAG, "decrypt error %d", err); |                 FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); | ||||||
|                     break; |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|                 err = picopass_worker_decrypt(AA1[8].data, pacs->pin0); |             err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); | ||||||
|             if(err != ERR_NONE) { |             if(err != ERR_NONE) { | ||||||
|                     FURI_LOG_E(TAG, "decrypt error %d", err); |                 FURI_LOG_E(TAG, "picopass_device_parse_wiegand 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; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             picopass_worker_parse_wiegand(pacs->credential, &pacs->record); |  | ||||||
| 
 |  | ||||||
|             // Notify caller and exit
 |             // Notify caller and exit
 | ||||||
|             if(picopass_worker->callback) { |             if(picopass_worker->callback) { | ||||||
|                 picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); |                 picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); | ||||||
|  | |||||||
| @ -6,6 +6,15 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <lib/toolbox/stream/file_stream.h> | #include <lib/toolbox/stream/file_stream.h> | ||||||
| 
 | 
 | ||||||
|  | #include <furi_hal.h> | ||||||
|  | 
 | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <st25r3916.h> | ||||||
|  | #include <rfal_analogConfig.h> | ||||||
|  | #include <rfal_rf.h> | ||||||
|  | 
 | ||||||
|  | #include <platform.h> | ||||||
|  | 
 | ||||||
| struct PicopassWorker { | struct PicopassWorker { | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     Storage* storage; |     Storage* storage; | ||||||
|  | |||||||
| @ -5,3 +5,7 @@ ADD_SCENE(picopass, card_menu, CardMenu) | |||||||
| ADD_SCENE(picopass, save_name, SaveName) | ADD_SCENE(picopass, save_name, SaveName) | ||||||
| ADD_SCENE(picopass, save_success, SaveSuccess) | ADD_SCENE(picopass, save_success, SaveSuccess) | ||||||
| ADD_SCENE(picopass, saved_menu, SavedMenu) | 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) | ||||||
|  | |||||||
							
								
								
									
										58
									
								
								applications/picopass/scenes/picopass_scene_delete.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								applications/picopass/scenes/picopass_scene_delete.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								applications/picopass/scenes/picopass_scene_delete_success.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										40
									
								
								applications/picopass/scenes/picopass_scene_delete_success.c
									
									
									
									
									
										Executable file
									
								
							| @ -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); | ||||||
|  | } | ||||||
							
								
								
									
										82
									
								
								applications/picopass/scenes/picopass_scene_device_info.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								applications/picopass/scenes/picopass_scene_device_info.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | |||||||
|  | #include "../picopass_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								applications/picopass/scenes/picopass_scene_file_select.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								applications/picopass/scenes/picopass_scene_file_select.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | } | ||||||
| @ -1,5 +1,11 @@ | |||||||
| #include "../picopass_i.h" | #include "../picopass_i.h" | ||||||
| 
 | 
 | ||||||
|  | enum SubmenuIndex { | ||||||
|  |     SubmenuIndexDelete, | ||||||
|  |     SubmenuIndexInfo, | ||||||
|  |     SubmenuIndexWrite, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { | void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { | ||||||
|     Picopass* picopass = context; |     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) { | void picopass_scene_saved_menu_on_enter(void* context) { | ||||||
|     Picopass* picopass = 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( |     submenu_set_selected_item( | ||||||
|         picopass->submenu, |         picopass->submenu, | ||||||
| @ -23,6 +39,14 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         scene_manager_set_scene_state( |         scene_manager_set_scene_state( | ||||||
|             picopass->scene_manager, PicopassSceneSavedMenu, event.event); |             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; |     return consumed; | ||||||
|  | |||||||
| @ -17,6 +17,8 @@ void picopass_scene_start_on_enter(void* context) { | |||||||
|     Submenu* submenu = picopass->submenu; |     Submenu* submenu = picopass->submenu; | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); |         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_set_selected_item( | ||||||
|         submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); |         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) { |         if(event.event == SubmenuIndexRead) { | ||||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); |             scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); | ||||||
|             consumed = true; |             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); |         scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ enum { | |||||||
|     RFAL_PICOPASS_CMD_READCHECK = 0x88, |     RFAL_PICOPASS_CMD_READCHECK = 0x88, | ||||||
|     RFAL_PICOPASS_CMD_CHECK = 0x05, |     RFAL_PICOPASS_CMD_CHECK = 0x05, | ||||||
|     RFAL_PICOPASS_CMD_READ = 0x0C, |     RFAL_PICOPASS_CMD_READ = 0x0C, | ||||||
|  |     RFAL_PICOPASS_CMD_WRITE = 0x0C, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -58,5 +59,6 @@ ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) | |||||||
| ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); | ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); | ||||||
| ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); | ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); | ||||||
| ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); | ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); | ||||||
|  | ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); | ||||||
| 
 | 
 | ||||||
| #endif /* RFAL_PICOPASS_H */ | #endif /* RFAL_PICOPASS_H */ | ||||||
|  | |||||||
| @ -158,3 +158,29 @@ ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRe | |||||||
|         fwt); |         fwt); | ||||||
|     return ret; |     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; | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Eric Betts
						Eric Betts