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,46 +220,15 @@ 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);
|
if(err != ERR_NONE) {
|
||||||
pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
|
FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Notify caller and exit
|
||||||
if(picopass_worker->callback) {
|
if(picopass_worker->callback) {
|
||||||
|
@ -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…
Reference in New Issue
Block a user