Просмотр исходного кода

[FL-3593] Picopass rework. Part 1 (#68)

Co-authored-by: あく <alleteam@gmail.com>
gornekich 2 лет назад
Родитель
Сommit
6ccba6c73e

+ 2 - 0
.catalog/changelog.md

@@ -1,3 +1,5 @@
+## 1.7
+ - Rework application with new NFC API
 ## 1.6
  - Faster loclass response collection
  - Save as LF for all bit lengths

+ 1 - 1
application.fam

@@ -10,7 +10,7 @@ App(
     ],
     stack_size=4 * 1024,
     fap_description="App to communicate with NFC tags using the PicoPass(iClass) format",
-    fap_version="1.6",
+    fap_version="1.7",
     fap_icon="125_10px.png",
     fap_category="NFC",
     fap_libs=["mbedtls"],

+ 4 - 5
picopass.c

@@ -23,7 +23,6 @@ void picopass_tick_event_callback(void* context) {
 Picopass* picopass_alloc() {
     Picopass* picopass = malloc(sizeof(Picopass));
 
-    picopass->worker = picopass_worker_alloc();
     picopass->view_dispatcher = view_dispatcher_alloc();
     picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass);
     view_dispatcher_enable_queue(picopass->view_dispatcher);
@@ -35,6 +34,8 @@ Picopass* picopass_alloc() {
     view_dispatcher_set_tick_event_callback(
         picopass->view_dispatcher, picopass_tick_event_callback, 100);
 
+    picopass->nfc = nfc_alloc();
+
     // Picopass device
     picopass->dev = picopass_device_alloc();
 
@@ -100,6 +101,8 @@ void picopass_free(Picopass* picopass) {
     picopass_device_free(picopass->dev);
     picopass->dev = NULL;
 
+    nfc_free(picopass->nfc);
+
     // Submenu
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu);
     submenu_free(picopass->submenu);
@@ -130,10 +133,6 @@ void picopass_free(Picopass* picopass) {
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoclass);
     loclass_free(picopass->loclass);
 
-    // Worker
-    picopass_worker_stop(picopass->worker);
-    picopass_worker_free(picopass->worker);
-
     // View Dispatcher
     view_dispatcher_free(picopass->view_dispatcher);
 

+ 8 - 27
picopass_device.c

@@ -225,8 +225,8 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo
         }
         if(!block_read) break;
 
-        if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break;
-        if(picopass_device_parse_wiegand(pacs->credential, pacs) != ERR_NONE) break;
+        picopass_device_parse_credential(AA1, pacs);
+        picopass_device_parse_wiegand(pacs->credential, pacs);
 
         parsed = true;
     } while(false);
@@ -343,7 +343,7 @@ void picopass_device_set_loading_callback(
     dev->loading_cb_ctx = context;
 }
 
-ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
+void 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;
@@ -351,35 +351,20 @@ ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
     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;
-
+void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
     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;
-        }
+        picopass_device_decrypt(AA1[7].data, pacs->credential);
 
-        err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "decrypt error %d", err);
-            return err;
-        }
+        picopass_device_decrypt(AA1[8].data, pacs->pin0);
 
-        err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "decrypt error %d", err);
-            return err;
-        }
+        picopass_device_decrypt(AA1[9].data, pacs->pin1);
     } else if(pacs->encryption == PicopassDeviceEncryptionNone) {
         FURI_LOG_D(TAG, "No Encryption");
         memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
@@ -392,11 +377,9 @@ ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pa
     }
 
     pacs->sio = (AA1[10].data[0] == 0x30); // rough check
-
-    return ERR_NONE;
 }
 
-ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) {
+void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) {
     uint32_t* halves = (uint32_t*)credential;
     if(halves[0] == 0) {
         uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
@@ -413,8 +396,6 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs
     swapped = swapped ^ sentinel;
     memcpy(credential, &swapped, sizeof(uint64_t));
     FURI_LOG_D(TAG, "PACS: (%d) %016llx", pacs->bitLength, swapped);
-
-    return ERR_NONE;
 }
 
 bool picopass_device_hid_csn(PicopassDevice* dev) {

+ 2 - 3
picopass_device.h

@@ -102,7 +102,6 @@ typedef struct {
 typedef struct {
     PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
     PicopassPacs pacs;
-    IclassEliteDictAttackData iclass_elite_dict_attack_data;
 } PicopassDeviceData;
 
 typedef struct {
@@ -147,6 +146,6 @@ void picopass_device_set_loading_callback(
     PicopassLoadingCallback callback,
     void* context);
 
-ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
-ReturnCode picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs);
+void picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
+void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs);
 bool picopass_device_hid_csn(PicopassDevice* dev);

+ 40 - 2
picopass_i.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #include "picopass.h"
-#include "picopass_worker.h"
 #include "picopass_device.h"
 
 #include "rfal_picopass.h"
@@ -29,8 +28,17 @@
 #include <lib/toolbox/path.h>
 #include <picopass_icons.h>
 
+#include <nfc/nfc.h>
+#include <nfc/helpers/nfc_dict.h>
+#include "protocol/picopass_poller.h"
+#include "protocol/picopass_listener.h"
+
 #define PICOPASS_TEXT_STORE_SIZE 128
 
+#define PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_elite_dict.txt")
+#define PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME APP_ASSETS_PATH("iclass_standard_dict.txt")
+#define PICOPASS_ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt")
+
 enum PicopassCustomEvent {
     // Reserve first 100 events for button types and indexes, starting from 0
     PicopassCustomEventReserved = 100,
@@ -40,6 +48,12 @@ enum PicopassCustomEvent {
     PicopassCustomEventByteInputDone,
     PicopassCustomEventTextInputDone,
     PicopassCustomEventDictAttackSkip,
+    PicopassCustomEventDictAttackUpdateView,
+    PicopassCustomEventLoclassGotMac,
+    PicopassCustomEventLoclassGotStandardKey,
+
+    PicopassCustomEventPollerSuccess,
+    PicopassCustomEventPollerFail,
 };
 
 typedef enum {
@@ -47,14 +61,34 @@ typedef enum {
     EventTypeKey,
 } EventType;
 
+typedef struct {
+    const char* name;
+    uint16_t total_keys;
+    uint16_t current_key;
+    bool card_detected;
+} PicopassDictAttackContext;
+
+typedef struct {
+    uint8_t key_to_write[PICOPASS_BLOCK_LEN];
+    bool is_elite;
+} PicopassWriteKeyContext;
+
+typedef struct {
+    size_t macs_collected;
+} PicopassLoclassContext;
+
 struct Picopass {
-    PicopassWorker* worker;
     ViewDispatcher* view_dispatcher;
     Gui* gui;
     NotificationApp* notifications;
     SceneManager* scene_manager;
     PicopassDevice* dev;
 
+    Nfc* nfc;
+    PicopassPoller* poller;
+    PicopassListener* listener;
+    NfcDict* dict;
+
     char text_store[PICOPASS_TEXT_STORE_SIZE + 1];
     FuriString* text_box_store;
     uint8_t byte_input_store[PICOPASS_BLOCK_LEN];
@@ -68,6 +102,10 @@ struct Picopass {
     Widget* widget;
     DictAttack* dict_attack;
     Loclass* loclass;
+
+    PicopassDictAttackContext dict_attack_ctx;
+    PicopassWriteKeyContext write_key_context;
+    PicopassLoclassContext loclass_context;
 };
 
 typedef enum {

+ 0 - 1336
picopass_worker.c

@@ -1,1336 +0,0 @@
-#include "picopass_worker_i.h"
-
-#include <flipper_format/flipper_format.h>
-#include <lib/nfc/protocols/nfcv.h>
-
-#define TAG "PicopassWorker"
-
-#define HAS_MASK(x, b) ((x & b) == b)
-
-// CSNs from Proxmark3 repo
-static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = {
-    {0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0},
-    {0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0},
-    {0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0},
-    {0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0},
-};
-
-static void picopass_worker_enable_field() {
-    furi_hal_nfc_exit_sleep();
-    furi_hal_nfc_ll_txrx_on();
-    furi_hal_nfc_ll_poll();
-}
-
-static ReturnCode picopass_worker_disable_field(ReturnCode rc) {
-    furi_hal_nfc_ll_txrx_off();
-    furi_hal_nfc_start_sleep();
-    return rc;
-}
-
-/***************************** Picopass Worker API *******************************/
-
-PicopassWorker* picopass_worker_alloc() {
-    PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker));
-
-    // Worker thread attributes
-    picopass_worker->thread =
-        furi_thread_alloc_ex("PicopassWorker", 8 * 1024, picopass_worker_task, picopass_worker);
-
-    picopass_worker->callback = NULL;
-    picopass_worker->context = NULL;
-    picopass_worker->storage = furi_record_open(RECORD_STORAGE);
-
-    picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
-
-    return picopass_worker;
-}
-
-void picopass_worker_free(PicopassWorker* picopass_worker) {
-    furi_assert(picopass_worker);
-
-    furi_thread_free(picopass_worker->thread);
-
-    furi_record_close(RECORD_STORAGE);
-
-    free(picopass_worker);
-}
-
-PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) {
-    return picopass_worker->state;
-}
-
-void picopass_worker_start(
-    PicopassWorker* picopass_worker,
-    PicopassWorkerState state,
-    PicopassDeviceData* dev_data,
-    PicopassWorkerCallback callback,
-    void* context) {
-    furi_assert(picopass_worker);
-    furi_assert(dev_data);
-
-    picopass_worker->callback = callback;
-    picopass_worker->context = context;
-    picopass_worker->dev_data = dev_data;
-    picopass_worker_change_state(picopass_worker, state);
-    furi_thread_start(picopass_worker->thread);
-}
-
-void picopass_worker_stop(PicopassWorker* picopass_worker) {
-    furi_assert(picopass_worker);
-    furi_assert(picopass_worker->thread);
-
-    if(furi_thread_get_state(picopass_worker->thread) == FuriThreadStateStopped) {
-        return;
-    }
-
-    if(picopass_worker->state == PicopassWorkerStateBroken ||
-       picopass_worker->state == PicopassWorkerStateReady) {
-        return;
-    }
-
-    if(picopass_worker->state != PicopassWorkerStateEmulate &&
-       picopass_worker->state != PicopassWorkerStateLoclass) {
-        // Can't do this while emulating in transparent mode as SPI isn't active
-        picopass_worker_disable_field(ERR_NONE);
-    }
-
-    if(furi_thread_get_state(picopass_worker->thread) != FuriThreadStateStopped) {
-        picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop);
-        furi_thread_join(picopass_worker->thread);
-    }
-}
-
-void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) {
-    picopass_worker->state = state;
-}
-
-/***************************** Picopass Worker Thread *******************************/
-
-ReturnCode picopass_detect_card(int timeout) {
-    UNUSED(timeout);
-
-    ReturnCode err;
-
-    err = rfalPicoPassPollerInitialize();
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err);
-        return err;
-    }
-
-    err = rfalFieldOnAndStartGT();
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerCheckPresence();
-    if(err != ERR_RF_COLLISION) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err);
-        return err;
-    }
-
-    return ERR_NONE;
-}
-
-ReturnCode picopass_read_preauth(PicopassBlock* AA1) {
-    rfalPicoPassIdentifyRes idRes;
-    rfalPicoPassSelectRes selRes;
-
-    ReturnCode err;
-
-    err = rfalPicoPassPollerIdentify(&idRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
-        return err;
-    }
-
-    memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, selRes.CSN, sizeof(selRes.CSN));
-    FURI_LOG_D(
-        TAG,
-        "csn %02x%02x%02x%02x%02x%02x%02x%02x",
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[0],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[1],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[2],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[3],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[4],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[5],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[6],
-        AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
-
-    rfalPicoPassReadBlockRes cfg = {0};
-    rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg);
-    memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data));
-    FURI_LOG_D(
-        TAG,
-        "config %02x%02x%02x%02x%02x%02x%02x%02x",
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
-        AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
-
-    rfalPicoPassReadBlockRes aia;
-    rfalPicoPassPollerReadBlock(PICOPASS_SECURE_AIA_BLOCK_INDEX, &aia);
-    memcpy(AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia.data, sizeof(aia.data));
-    FURI_LOG_D(
-        TAG,
-        "aia %02x%02x%02x%02x%02x%02x%02x%02x",
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6],
-        AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]);
-
-    return ERR_NONE;
-}
-
-static ReturnCode
-    picopass_auth_dict(PicopassWorker* picopass_worker, IclassEliteDictType dict_type) {
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-    bool elite = (dict_type != IclassStandardDictTypeFlipper);
-
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-
-    uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
-    uint8_t* div_key = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
-
-    ReturnCode err = ERR_PARAM;
-
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    size_t index = 0;
-    uint8_t key[PICOPASS_BLOCK_LEN] = {0};
-
-    if(!iclass_elite_dict_check_presence(dict_type)) {
-        FURI_LOG_E(TAG, "Dictionary not found");
-        return ERR_PARAM;
-    }
-
-    IclassEliteDict* dict = iclass_elite_dict_alloc(dict_type);
-    if(!dict) {
-        FURI_LOG_E(TAG, "Dictionary not allocated");
-        return ERR_PARAM;
-    }
-
-    FURI_LOG_D(TAG, "Loaded %lu keys", iclass_elite_dict_get_total_keys(dict));
-    while(iclass_elite_dict_get_next_key(dict, key)) {
-        FURI_LOG_D(
-            TAG,
-            "Try to %s auth with key %zu %02x%02x%02x%02x%02x%02x%02x%02x",
-            elite ? "elite" : "standard",
-            index++,
-            key[0],
-            key[1],
-            key[2],
-            key[3],
-            key[4],
-            key[5],
-            key[6],
-            key[7]);
-
-        err = rfalPicoPassPollerReadCheck(&rcRes);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-            break;
-        }
-        memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-        loclass_iclass_calc_div_key(csn, key, div_key, elite);
-        loclass_opt_doReaderMAC(ccnr, div_key, mac);
-
-        err = rfalPicoPassPollerCheck(mac, &chkRes);
-        if(err == ERR_NONE) {
-            memcpy(pacs->key, key, PICOPASS_BLOCK_LEN);
-            break;
-        }
-
-        if(picopass_worker->state != PicopassWorkerStateDetect) break;
-    }
-
-    iclass_elite_dict_free(dict);
-
-    return err;
-}
-
-ReturnCode picopass_auth(PicopassWorker* picopass_worker) {
-    ReturnCode err;
-
-    FURI_LOG_I(TAG, "Starting system dictionary attack [Standard KDF]");
-    err = picopass_auth_dict(picopass_worker, IclassStandardDictTypeFlipper);
-    if(err == ERR_NONE) {
-        return ERR_NONE;
-    }
-
-    /* Because size of the user dictionary and could introduce confusing delay
-     * to the read screen (since there is no feedback), we omit checking it.
-     * It will be checked when the user uses Elite Dict. Attack, which has a progress bar
-     */
-
-    FURI_LOG_I(TAG, "Starting system dictionary attack [Elite KDF]");
-    err = picopass_auth_dict(picopass_worker, IclassEliteDictTypeFlipper);
-    if(err == ERR_NONE) {
-        return ERR_NONE;
-    }
-
-    return err;
-}
-
-ReturnCode picopass_read_card(PicopassBlock* AA1) {
-    ReturnCode err;
-
-    size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ?
-                           AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
-                           PICOPASS_MAX_APP_LIMIT;
-
-    for(size_t i = 2; i < app_limit; i++) {
-        if(i == PICOPASS_SECURE_KD_BLOCK_INDEX) {
-            // Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
-            continue;
-        }
-
-        rfalPicoPassReadBlockRes block;
-        err = rfalPicoPassPollerReadBlock(i, &block);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err);
-            return err;
-        }
-
-        FURI_LOG_D(
-            TAG,
-            "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x",
-            i,
-            block.data[0],
-            block.data[1],
-            block.data[2],
-            block.data[3],
-            block.data[4],
-            block.data[5],
-            block.data[6],
-            block.data[7]);
-
-        memcpy(AA1[i].data, block.data, sizeof(block.data));
-    }
-
-    return ERR_NONE;
-}
-
-ReturnCode picopass_write_card(PicopassBlock* AA1) {
-    rfalPicoPassIdentifyRes idRes;
-    rfalPicoPassSelectRes selRes;
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-
-    ReturnCode err;
-
-    uint8_t div_key[8] = {0};
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    err = rfalPicoPassPollerIdentify(&idRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerReadCheck(&rcRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-        return err;
-    }
-    memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-    loclass_iclass_calc_div_key(selRes.CSN, (uint8_t*)picopass_iclass_key, div_key, false);
-    loclass_opt_doReaderMAC(ccnr, div_key, mac);
-
-    err = rfalPicoPassPollerCheck(mac, &chkRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
-        return err;
-    }
-
-    for(size_t i = 6; i < 10; i++) {
-        FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", i);
-        uint8_t data[9] = {0};
-        data[0] = i;
-        memcpy(data + 1, AA1[i].data, PICOPASS_BLOCK_LEN);
-        loclass_doMAC_N(data, sizeof(data), div_key, mac);
-        FURI_LOG_D(
-            TAG,
-            "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
-            i,
-            data[1],
-            data[2],
-            data[3],
-            data[4],
-            data[5],
-            data[6],
-            data[7],
-            data[8],
-            mac[0],
-            mac[1],
-            mac[2],
-            mac[3]);
-
-        err = rfalPicoPassPollerWriteBlock(i, AA1[i].data, mac);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
-            return err;
-        }
-    }
-
-    return ERR_NONE;
-}
-
-ReturnCode picopass_write_block(PicopassBlock* AA1, uint8_t blockNo, uint8_t* newBlock) {
-    rfalPicoPassIdentifyRes idRes;
-    rfalPicoPassSelectRes selRes;
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-
-    ReturnCode err;
-
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    err = rfalPicoPassPollerIdentify(&idRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
-        return err;
-    }
-
-    err = rfalPicoPassPollerReadCheck(&rcRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-        return err;
-    }
-    memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-    if(memcmp(selRes.CSN, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN) != 0) {
-        FURI_LOG_E(TAG, "Wrong CSN for write");
-        return ERR_REQUEST;
-    }
-
-    loclass_opt_doReaderMAC(ccnr, AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, mac);
-    err = rfalPicoPassPollerCheck(mac, &chkRes);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
-        return err;
-    }
-
-    FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo);
-    uint8_t data[9] = {
-        blockNo,
-        newBlock[0],
-        newBlock[1],
-        newBlock[2],
-        newBlock[3],
-        newBlock[4],
-        newBlock[5],
-        newBlock[6],
-        newBlock[7]};
-    loclass_doMAC_N(data, sizeof(data), AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, mac);
-    FURI_LOG_D(
-        TAG,
-        "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
-        blockNo,
-        data[1],
-        data[2],
-        data[3],
-        data[4],
-        data[5],
-        data[6],
-        data[7],
-        data[8],
-        mac[0],
-        mac[1],
-        mac[2],
-        mac[3]);
-
-    err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac);
-    if(err != ERR_NONE) {
-        FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
-        return err;
-    }
-
-    return ERR_NONE;
-}
-
-void picopass_worker_elite_dict_attack(PicopassWorker* picopass_worker) {
-    furi_assert(picopass_worker);
-    furi_assert(picopass_worker->callback);
-
-    picopass_device_data_clear(picopass_worker->dev_data);
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-
-    for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
-        memset(AA1[i].data, 0, sizeof(AA1[i].data));
-    }
-    memset(pacs, 0, sizeof(PicopassPacs));
-
-    IclassEliteDictAttackData* dict_attack_data =
-        &picopass_worker->dev_data->iclass_elite_dict_attack_data;
-    bool elite = (dict_attack_data->type != IclassStandardDictTypeFlipper);
-
-    rfalPicoPassReadCheckRes rcRes;
-    rfalPicoPassCheckRes chkRes;
-
-    ReturnCode err;
-    uint8_t mac[4] = {0};
-    uint8_t ccnr[12] = {0};
-
-    size_t index = 0;
-    uint8_t key[PICOPASS_BLOCK_LEN] = {0};
-
-    // Load dictionary
-    IclassEliteDict* dict = dict_attack_data->dict;
-    if(!dict) {
-        FURI_LOG_E(TAG, "Dictionary not found");
-        picopass_worker->callback(PicopassWorkerEventNoDictFound, picopass_worker->context);
-        return;
-    }
-
-    do {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            picopass_worker->callback(PicopassWorkerEventCardDetected, picopass_worker->context);
-
-            // Process first found device
-            err = picopass_read_preauth(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_read_preauth error %d", err);
-                picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-                return;
-            }
-
-            // Thank you proxmark!
-            pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8);
-            pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
-            if(pacs->se_enabled) {
-                FURI_LOG_D(TAG, "SE enabled");
-                picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-                return;
-            }
-
-            break;
-        } else {
-            picopass_worker->callback(PicopassWorkerEventNoCardDetected, picopass_worker->context);
-        }
-        if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break;
-
-        furi_delay_ms(100);
-    } while(true);
-
-    FURI_LOG_D(
-        TAG, "Start Dictionary attack, Key Count %lu", iclass_elite_dict_get_total_keys(dict));
-    while(iclass_elite_dict_get_next_key(dict, key)) {
-        FURI_LOG_T(TAG, "Key %zu", index);
-        if(++index % PICOPASS_DICT_KEY_BATCH_SIZE == 0) {
-            picopass_worker->callback(
-                PicopassWorkerEventNewDictKeyBatch, picopass_worker->context);
-        }
-
-        err = rfalPicoPassPollerReadCheck(&rcRes);
-        if(err != ERR_NONE) {
-            FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
-            break;
-        }
-        memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
-
-        uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
-        uint8_t* div_key = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
-
-        loclass_iclass_calc_div_key(csn, key, div_key, elite);
-        loclass_opt_doReaderMAC(ccnr, div_key, mac);
-
-        err = rfalPicoPassPollerCheck(mac, &chkRes);
-        if(err == ERR_NONE) {
-            FURI_LOG_I(
-                TAG,
-                "Found key: %02x%02x%02x%02x%02x%02x%02x%02x",
-                key[0],
-                key[1],
-                key[2],
-                key[3],
-                key[4],
-                key[5],
-                key[6],
-                key[7]);
-
-            memcpy(pacs->key, key, PICOPASS_BLOCK_LEN);
-            pacs->elite_kdf = elite;
-            err = picopass_read_card(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_read_card error %d", err);
-                picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context);
-                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->callback(PicopassWorkerEventFail, picopass_worker->context);
-                break;
-            }
-
-            err = picopass_device_parse_wiegand(pacs->credential, pacs);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
-                picopass_worker->callback(PicopassWorkerEventFail, picopass_worker->context);
-                break;
-            }
-            picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-            break;
-        }
-
-        if(picopass_worker->state != PicopassWorkerStateEliteDictAttack) break;
-    }
-    FURI_LOG_D(TAG, "Dictionary complete");
-    if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) {
-        picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context);
-    } else {
-        picopass_worker->callback(PicopassWorkerEventAborted, picopass_worker->context);
-    }
-}
-
-int32_t picopass_worker_task(void* context) {
-    PicopassWorker* picopass_worker = context;
-
-    if(picopass_worker->state == PicopassWorkerStateDetect) {
-        picopass_worker_enable_field();
-        picopass_worker_detect(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateWrite) {
-        picopass_worker_enable_field();
-        picopass_worker_write(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateWriteKey) {
-        picopass_worker_enable_field();
-        picopass_worker_write_key(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateEliteDictAttack) {
-        picopass_worker_enable_field();
-        picopass_worker_elite_dict_attack(picopass_worker);
-    } else if(picopass_worker->state == PicopassWorkerStateEmulate) {
-        picopass_worker_emulate(picopass_worker, false);
-    } else if(picopass_worker->state == PicopassWorkerStateLoclass) {
-        picopass_worker_emulate(picopass_worker, true);
-    } else if(picopass_worker->state == PicopassWorkerStateStop) {
-        FURI_LOG_D(TAG, "Worker state stop");
-        // no-op
-    } else {
-        FURI_LOG_W(TAG, "Unknown state %d", picopass_worker->state);
-    }
-    picopass_worker_disable_field(ERR_NONE);
-    picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
-
-    return 0;
-}
-
-void picopass_worker_detect(PicopassWorker* picopass_worker) {
-    picopass_device_data_clear(picopass_worker->dev_data);
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-    ReturnCode err;
-
-    // reset device data
-    for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
-        memset(AA1[i].data, 0, sizeof(AA1[i].data));
-    }
-    memset(pacs, 0, sizeof(PicopassPacs));
-
-    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
-
-    while(picopass_worker->state == PicopassWorkerStateDetect) {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            // Process first found device
-            err = picopass_read_preauth(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_read_preauth error %d", err);
-                nextState = PicopassWorkerEventFail;
-            }
-
-            // Thank you proxmark!
-            pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8);
-            pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
-            if(pacs->se_enabled) {
-                FURI_LOG_D(TAG, "SE enabled");
-                nextState = PicopassWorkerEventFail;
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_auth(picopass_worker);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_try_auth error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_read_card(AA1);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_read_card error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_device_parse_credential(AA1, pacs);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            if(nextState == PicopassWorkerEventSuccess) {
-                err = picopass_device_parse_wiegand(pacs->credential, pacs);
-                if(err != ERR_NONE) {
-                    FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
-                    nextState = PicopassWorkerEventFail;
-                }
-            }
-
-            // Notify caller and exit
-            if(picopass_worker->callback) {
-                picopass_worker->callback(nextState, picopass_worker->context);
-            }
-            break;
-        }
-        furi_delay_ms(100);
-    }
-}
-
-void picopass_worker_write(PicopassWorker* picopass_worker) {
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    ReturnCode err;
-    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
-
-    while(picopass_worker->state == PicopassWorkerStateWrite) {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            err = picopass_write_card(AA1);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_write_card error %d", err);
-                nextState = PicopassWorkerEventFail;
-            }
-
-            // Notify caller and exit
-            if(picopass_worker->callback) {
-                picopass_worker->callback(nextState, picopass_worker->context);
-            }
-            break;
-        }
-        furi_delay_ms(100);
-    }
-}
-
-void picopass_worker_write_key(PicopassWorker* picopass_worker) {
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* AA1 = dev_data->AA1;
-    PicopassPacs* pacs = &dev_data->pacs;
-    ReturnCode err;
-    PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
-
-    uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data;
-    uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
-    uint8_t fuses = configBlock[7];
-    uint8_t* oldKey = AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
-
-    uint8_t newKey[PICOPASS_BLOCK_LEN] = {0};
-    loclass_iclass_calc_div_key(csn, pacs->key, newKey, pacs->elite_kdf);
-
-    if((fuses & 0x80) == 0x80) {
-        FURI_LOG_D(TAG, "Plain write for personalized mode key change");
-    } else {
-        FURI_LOG_D(TAG, "XOR write for application mode key change");
-        // XOR when in application mode
-        for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
-            newKey[i] ^= oldKey[i];
-        }
-    }
-
-    while(picopass_worker->state == PicopassWorkerStateWriteKey) {
-        if(picopass_detect_card(1000) == ERR_NONE) {
-            err = picopass_write_block(AA1, PICOPASS_SECURE_KD_BLOCK_INDEX, newKey);
-            if(err != ERR_NONE) {
-                FURI_LOG_E(TAG, "picopass_write_block error %d", err);
-                nextState = PicopassWorkerEventFail;
-            }
-
-            // Notify caller and exit
-            if(picopass_worker->callback) {
-                picopass_worker->callback(nextState, picopass_worker->context);
-            }
-            break;
-        }
-        furi_delay_ms(100);
-    }
-}
-
-// from proxmark3 armsrc/iclass.c rotateCSN
-static void picopass_anticoll_csn(uint8_t* rotated_csn, const uint8_t* original_csn) {
-    for(uint8_t i = 0; i < 8; i++) {
-        rotated_csn[i] = (original_csn[i] >> 3) | (original_csn[(i + 1) % 8] << 5);
-    }
-}
-
-static void picopass_append_crc(uint8_t* buf, uint16_t size) {
-    uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, buf, size);
-
-    buf[size] = crc & 0xFF;
-    buf[size + 1] = crc >> 8;
-}
-
-static inline void picopass_emu_read_blocks(
-    NfcVData* nfcv_data,
-    uint8_t* buf,
-    uint8_t block_num,
-    uint8_t block_count) {
-    memcpy(
-        buf, nfcv_data->data + (block_num * PICOPASS_BLOCK_LEN), block_count * PICOPASS_BLOCK_LEN);
-}
-
-static inline void picopass_emu_write_blocks(
-    NfcVData* nfcv_data,
-    const uint8_t* buf,
-    uint8_t block_num,
-    uint8_t block_count) {
-    memcpy(
-        nfcv_data->data + (block_num * PICOPASS_BLOCK_LEN), buf, block_count * PICOPASS_BLOCK_LEN);
-}
-
-static void picopass_init_cipher_state_key(
-    NfcVData* nfcv_data,
-    PicopassEmulatorCtx* ctx,
-    const uint8_t key[PICOPASS_BLOCK_LEN]) {
-    uint8_t cc[PICOPASS_BLOCK_LEN];
-    picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-    ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
-}
-
-static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
-    uint8_t key[PICOPASS_BLOCK_LEN];
-
-    picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
-
-    picopass_init_cipher_state_key(nfcv_data, ctx, key);
-}
-
-static void
-    loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
-    // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
-    const uint8_t* csn =
-        loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
-    memcpy(nfc_data->uid, csn, PICOPASS_BLOCK_LEN);
-    picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);
-
-    uint8_t key[PICOPASS_BLOCK_LEN];
-    loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
-    picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
-
-    picopass_init_cipher_state_key(nfcv_data, ctx, key);
-}
-
-static void picopass_emu_handle_packet(
-    FuriHalNfcTxRxContext* tx_rx,
-    FuriHalNfcDevData* nfc_data,
-    void* nfcv_data_in) {
-    NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
-    PicopassEmulatorCtx* ctx = nfcv_data->emu_protocol_ctx;
-    uint8_t response[34];
-    uint8_t response_length = 0;
-    uint8_t key_block_num = PICOPASS_SECURE_KD_BLOCK_INDEX;
-
-    const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-
-    if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
-        return;
-    }
-
-    switch(nfcv_data->frame[0]) {
-    case RFAL_PICOPASS_CMD_ACTALL: // No args
-        if(nfcv_data->frame_length != 1) {
-            return;
-        }
-
-        if(ctx->state != PicopassEmulatorStateHalt) {
-            ctx->state = PicopassEmulatorStateActive;
-        }
-
-        // Send SOF only
-        break;
-    case RFAL_PICOPASS_CMD_ACT: // No args
-        if(nfcv_data->frame_length != 1 || ctx->state != PicopassEmulatorStateActive) {
-            return;
-        }
-
-        // Send SOF only
-        break;
-    case RFAL_PICOPASS_CMD_HALT: // No args
-        if(nfcv_data->frame_length != 1 || ctx->state != PicopassEmulatorStateSelected) {
-            return;
-        }
-
-        // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
-        ctx->state = PicopassEmulatorStateIdle;
-
-        // Send SOF only
-        break;
-    case RFAL_PICOPASS_CMD_READ_OR_IDENTIFY:
-        if(nfcv_data->frame_length == 1 &&
-           ctx->state == PicopassEmulatorStateActive) { // PICOPASS_CMD_IDENTIFY
-            // ASNB(8) CRC16(2)
-            picopass_anticoll_csn(response, nfc_data->uid);
-            picopass_append_crc(response, PICOPASS_BLOCK_LEN);
-            response_length = PICOPASS_BLOCK_LEN + 2;
-            break;
-        } else if(
-            nfcv_data->frame_length == 4 &&
-            ctx->state == PicopassEmulatorStateSelected) { // PICOPASS_CMD_READ ADDRESS(1) CRC16(2)
-            if(nfcv_data->frame[1] >= PICOPASS_MAX_APP_LIMIT) {
-                return;
-            }
-
-            // TODO: Check CRC?
-            // TODO: Check auth?
-
-            // DATA(8) CRC16(2)
-            if(nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX ||
-               nfcv_data->frame[1] == PICOPASS_SECURE_KC_BLOCK_INDEX) {
-                // Reading Kd or Kc blocks always returns FF's
-                memcpy(response, block_ff, PICOPASS_BLOCK_LEN);
-            } else {
-                picopass_emu_read_blocks(nfcv_data, response, nfcv_data->frame[1], 1);
-            }
-            picopass_append_crc(response, PICOPASS_BLOCK_LEN);
-            response_length = PICOPASS_BLOCK_LEN + 2;
-            break;
-        }
-
-        return;
-    case RFAL_PICOPASS_CMD_READ4: // ADDRESS(1) CRC16(2)
-        if(nfcv_data->frame_length != 4 || ctx->state != PicopassEmulatorStateSelected ||
-           nfcv_data->frame[1] + 4 >= PICOPASS_MAX_APP_LIMIT) {
-            return;
-        }
-
-        // TODO: Check CRC?
-        // TODO: Check auth?
-
-        uint8_t blockNum = nfcv_data->frame[1];
-
-        // DATA(32) CRC16(2)
-        picopass_emu_read_blocks(nfcv_data, response, blockNum, 4);
-        if(blockNum == 4) {
-            // Kc is block 4, so just redact first block of response
-            memcpy(response, block_ff, PICOPASS_BLOCK_LEN);
-        } else if(blockNum < 4) {
-            // Kd is block 3
-            uint8_t* kdOffset = response + ((3 - blockNum) * PICOPASS_BLOCK_LEN);
-            memcpy(kdOffset, block_ff, PICOPASS_BLOCK_LEN);
-            if(blockNum != 0) {
-                // Redact Kc
-                memcpy(kdOffset + PICOPASS_BLOCK_LEN, block_ff, PICOPASS_BLOCK_LEN);
-            }
-        }
-        picopass_append_crc(response, PICOPASS_BLOCK_LEN * 4);
-        response_length = (PICOPASS_BLOCK_LEN * 4) + 2;
-        break;
-    case RFAL_PICOPASS_CMD_SELECT: // ASNB(8)|SERIALNB(8)
-        if(nfcv_data->frame_length != 9) {
-            return;
-        }
-
-        uint8_t select_csn[PICOPASS_BLOCK_LEN];
-        if(ctx->state == PicopassEmulatorStateHalt || ctx->state == PicopassEmulatorStateIdle) {
-            memcpy(select_csn, nfc_data->uid, PICOPASS_BLOCK_LEN);
-        } else {
-            picopass_anticoll_csn(select_csn, nfc_data->uid);
-        }
-
-        if(memcmp(nfcv_data->frame + 1, select_csn, PICOPASS_BLOCK_LEN)) {
-            if(ctx->state == PicopassEmulatorStateActive) {
-                ctx->state = PicopassEmulatorStateIdle;
-            } else if(ctx->state == PicopassEmulatorStateSelected) {
-                // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
-                ctx->state = PicopassEmulatorStateIdle;
-            }
-
-            return;
-        }
-
-        ctx->state = PicopassEmulatorStateSelected;
-
-        // SERIALNB(8) CRC16(2)
-        memcpy(response, nfc_data->uid, PICOPASS_BLOCK_LEN);
-        picopass_append_crc(response, PICOPASS_BLOCK_LEN);
-
-        response_length = PICOPASS_BLOCK_LEN + 2;
-        break;
-    case RFAL_PICOPASS_CMD_READCHECK_KC: // ADDRESS(1)
-        key_block_num = PICOPASS_SECURE_KC_BLOCK_INDEX;
-        // fallthrough
-    case RFAL_PICOPASS_CMD_READCHECK_KD: // ADDRESS(1)
-        if(nfcv_data->frame_length != 2 ||
-           nfcv_data->frame[1] != PICOPASS_SECURE_EPURSE_BLOCK_INDEX ||
-           ctx->state != PicopassEmulatorStateSelected) {
-            return;
-        }
-
-        // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
-        // we can also no-op if the key block is the same, CHECK re-inits if it failed already
-        if(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
-            ctx->key_block_num = key_block_num;
-            picopass_init_cipher_state(nfcv_data, ctx);
-        }
-
-        // DATA(8)
-        picopass_emu_read_blocks(nfcv_data, response, nfcv_data->frame[1], 1);
-        response_length = PICOPASS_BLOCK_LEN;
-        break;
-    case RFAL_PICOPASS_CMD_CHECK: // CHALLENGE(4) READERSIGNATURE(4)
-        if(nfcv_data->frame_length != 9 || ctx->state != PicopassEmulatorStateSelected) {
-            return;
-        }
-
-        if(ctx->loclass_mode) {
-            // LOCLASS Reader attack mode
-
-            // Copy EPURSE
-            uint8_t cc[PICOPASS_BLOCK_LEN];
-            picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
-            uint8_t key[PICOPASS_BLOCK_LEN];
-            // loclass mode stores the derived standard debit key in Kd to check
-            picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);
-
-            uint8_t rmac[4];
-            loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key);
-
-            if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
-                // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
-                // Either way no point logging it.
-
-                FURI_LOG_W(TAG, "loclass: standard key detected during collection");
-                ctx->loclass_got_std_key = true;
-
-                // Don't reset the state as the reader may try a different key next without going through anticoll
-                // The reader is always free to redo the anticoll if it wants to anyway
-
-                return;
-            }
-#endif
-
-            // Save to buffer to defer flushing when we rotate CSN
-            memcpy(
-                ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
-                nfcv_data->frame + 1,
-                8);
-
-            // Rotate to the next CSN/attempt
-            ctx->key_block_num++;
-
-            // CSN changed
-            if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
-                // Flush NR-MACs for this CSN to SD card
-                uint8_t cc[PICOPASS_BLOCK_LEN];
-                picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-                for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
-                    loclass_writer_write_params(
-                        ctx->loclass_writer,
-                        ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
-                        nfc_data->uid,
-                        cc,
-                        ctx->loclass_mac_buffer + (i * 8),
-                        ctx->loclass_mac_buffer + (i * 8) + 4);
-                }
-
-                if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
-                    loclass_update_csn(nfc_data, nfcv_data, ctx);
-                    // Only reset the state when we change to a new CSN for the same reason as when we get a standard key
-                    ctx->state = PicopassEmulatorStateIdle;
-                } else {
-                    ctx->state = PicopassEmulatorStateStopEmulation;
-                }
-            }
-
-            return;
-        }
-
-        uint8_t key[PICOPASS_BLOCK_LEN];
-        picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);
-
-        uint8_t rmac[4];
-        loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);
-
-#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
-        if(memcmp(nfcv_data->frame + 5, rmac, 4)) {
-            // Bad MAC from reader, do not send a response.
-            FURI_LOG_I(TAG, "Got bad MAC from reader");
-            // Reset the cipher state since we don't do it in READCHECK
-            picopass_init_cipher_state(nfcv_data, ctx);
-            return;
-        }
-#endif
-
-        // CHIPRESPONSE(4)
-        response_length = 4;
-        break;
-    case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
-        if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
-           ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
-            return;
-        }
-
-        if(nfcv_data->frame[1] >= PICOPASS_MAX_APP_LIMIT) {
-            return;
-        }
-
-        uint8_t cfgBlock[PICOPASS_BLOCK_LEN];
-        picopass_emu_read_blocks(nfcv_data, cfgBlock, PICOPASS_CONFIG_BLOCK_INDEX, 1);
-        bool persMode = HAS_MASK(cfgBlock[7], PICOPASS_FUSE_PERS);
-
-        if((nfcv_data->frame[1] == PICOPASS_CSN_BLOCK_INDEX) // CSN is always read only
-           ||
-           (!persMode &&
-            !HAS_MASK(cfgBlock[3], 0x80)) // Chip is in RO mode, no updated possible (even ePurse)
-           || (!persMode &&
-               nfcv_data->frame[1] ==
-                   PICOPASS_SECURE_AIA_BLOCK_INDEX) // AIA can only be set in personalisation mode
-           || (!persMode &&
-               (nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX ||
-                nfcv_data->frame[1] == PICOPASS_SECURE_KC_BLOCK_INDEX) &&
-               (!HAS_MASK(cfgBlock[7], PICOPASS_FUSE_CRYPT10)))) {
-            return; // TODO: Is this the right response?
-        }
-
-        if(nfcv_data->frame[1] >= 6 && nfcv_data->frame[1] <= 12) {
-            if(!HAS_MASK(
-                   cfgBlock[3],
-                   1 << (nfcv_data->frame[1] - 6))) { // bit0 is block6, up to bit6 being block12
-                // Block is marked as read-only, deny writing
-                return; // TODO: Is this the right response?
-            }
-        }
-
-        // TODO: Check CRC/SIGN depending on if in secure mode
-        // Check correct key
-        // -> Kd only allows decrementing e-Purse
-        // -> per-app controlled by key access config
-        //bool keyAccess = HAS_MASK(cfgBlock[5], 0x01);
-        // -> must auth with that key to change it
-
-        uint8_t blockOffset = nfcv_data->frame[1];
-        uint8_t block[PICOPASS_BLOCK_LEN];
-        switch(nfcv_data->frame[1]) {
-        case PICOPASS_CONFIG_BLOCK_INDEX:
-            block[0] = cfgBlock[0]; // Applications Limit
-            block[1] = cfgBlock[1] & nfcv_data->frame[3]; // OTP
-            block[2] = cfgBlock[2] & nfcv_data->frame[4]; // OTP
-            block[3] = cfgBlock[3] & nfcv_data->frame[5]; // Block Write Lock
-            block[4] = cfgBlock[4]; // Chip Config
-            block[5] = cfgBlock[5]; // Memory Config
-            block[6] = nfcv_data->frame[8]; // EAS
-            block[7] = cfgBlock[7]; // Fuses
-
-            // Some parts allow w (but not e) if in persMode
-            if(persMode) {
-                block[0] &= nfcv_data->frame[2]; // Applications Limit
-                block[4] &= nfcv_data->frame[6]; // Chip Config
-                block[5] &= nfcv_data->frame[7]; // Memory Config
-                block[7] &= nfcv_data->frame[9]; // Fuses
-            } else {
-                // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode
-                block[7] &= nfcv_data->frame[9] | ~PICOPASS_FUSE_CRYPT10;
-            }
-            break;
-        case PICOPASS_SECURE_EPURSE_BLOCK_INDEX:
-            // ePurse updates swap first and second half of the block each update
-            memcpy(block + 4, nfcv_data->frame + 2, 4);
-            memcpy(block, nfcv_data->frame + 6, 4);
-            break;
-        case PICOPASS_SECURE_KD_BLOCK_INDEX:
-            // fallthrough
-        case PICOPASS_SECURE_KC_BLOCK_INDEX:
-            if(!persMode) {
-                picopass_emu_read_blocks(nfcv_data, block, blockOffset, 1);
-                for(uint8_t i = 0; i < sizeof(PICOPASS_BLOCK_LEN); i++)
-                    block[i] ^= nfcv_data->frame[i + 2];
-                break;
-            }
-            // Use default case when in personalisation mode
-            // fallthrough
-        default:
-            memcpy(block, nfcv_data->frame + 2, PICOPASS_BLOCK_LEN);
-            break;
-        }
-
-        picopass_emu_write_blocks(nfcv_data, block, blockOffset, 1);
-
-        if((nfcv_data->frame[1] == ctx->key_block_num ||
-            nfcv_data->frame[1] == PICOPASS_SECURE_EPURSE_BLOCK_INDEX) &&
-           !ctx->loclass_mode)
-            picopass_init_cipher_state(nfcv_data, ctx);
-
-        // DATA(8) CRC16(2)
-        if(nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX ||
-           nfcv_data->frame[1] == PICOPASS_SECURE_KD_BLOCK_INDEX) {
-            // Key updates always return FF's
-            memcpy(response, block_ff, PICOPASS_BLOCK_LEN);
-        } else {
-            memcpy(response, block, PICOPASS_BLOCK_LEN);
-        }
-        picopass_append_crc(response, PICOPASS_BLOCK_LEN);
-        response_length = PICOPASS_BLOCK_LEN + 2;
-        break;
-    case RFAL_PICOPASS_CMD_PAGESEL: // PAGE(1) CRC16(2)
-        // Chips with a single page do not answer to this command
-        // BLOCK1(8) CRC16(2)
-        return;
-    case RFAL_PICOPASS_CMD_DETECT:
-        // TODO - not used by iClass though
-        return;
-    default:
-        return;
-    }
-
-    NfcVSendFlags flags = NfcVSendFlagsSof | NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate;
-    if(response_length > 0) {
-        flags |= NfcVSendFlagsEof;
-    }
-
-    nfcv_emu_send(
-        tx_rx,
-        nfcv_data,
-        response,
-        response_length,
-        flags,
-        nfcv_data->eof_timestamp + NFCV_FDT_FC(4000)); // 3650 is ~254uS 4000 is ~283uS
-}
-
-void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode) {
-    furi_hal_nfc_exit_sleep();
-
-    FuriHalNfcTxRxContext tx_rx = {};
-    PicopassEmulatorCtx emu_ctx = {
-        .state = PicopassEmulatorStateIdle,
-        .key_block_num = PICOPASS_SECURE_KD_BLOCK_INDEX,
-        .loclass_mode = loclass_mode,
-        .loclass_got_std_key = false,
-        .loclass_writer = NULL,
-    };
-    FuriHalNfcDevData nfc_data = {
-        .uid_len = RFAL_PICOPASS_UID_LEN,
-    };
-    NfcVData* nfcv_data = malloc(sizeof(NfcVData));
-    nfcv_data->block_size = PICOPASS_BLOCK_LEN;
-    nfcv_data->emu_protocol_ctx = &emu_ctx;
-    nfcv_data->emu_protocol_handler = &picopass_emu_handle_packet;
-
-    PicopassDeviceData* dev_data = picopass_worker->dev_data;
-    PicopassBlock* blocks = dev_data->AA1;
-
-    if(loclass_mode) {
-        emu_ctx.loclass_writer = loclass_writer_alloc();
-        if(emu_ctx.loclass_writer == NULL) {
-            picopass_worker->callback(
-                PicopassWorkerEventLoclassFileError, picopass_worker->context);
-
-            while(picopass_worker->state == PicopassWorkerStateEmulate ||
-                  picopass_worker->state == PicopassWorkerStateLoclass) {
-                furi_delay_ms(1);
-            }
-
-            free(nfcv_data);
-
-            return;
-        }
-
-        // Setup blocks for loclass attack
-        uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
-        picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);
-
-        uint8_t epurse[8] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-        picopass_emu_write_blocks(nfcv_data, epurse, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
-
-        uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-        picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);
-
-        emu_ctx.key_block_num = 0;
-        loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
-
-        loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
-    } else {
-        memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
-        memcpy(nfcv_data->data, blocks, sizeof(dev_data->AA1));
-        picopass_init_cipher_state(nfcv_data, &emu_ctx);
-    }
-
-    uint8_t last_loclass_csn_num = 0;
-    bool loclass_got_std_key = false;
-
-    nfcv_emu_init(&nfc_data, nfcv_data);
-    while(picopass_worker->state == PicopassWorkerStateEmulate ||
-          picopass_worker->state == PicopassWorkerStateLoclass) {
-        if(nfcv_emu_loop(&tx_rx, &nfc_data, nfcv_data, 500)) {
-            if(picopass_worker->callback) {
-                if((loclass_mode) && (last_loclass_csn_num != emu_ctx.key_block_num)) {
-                    last_loclass_csn_num = emu_ctx.key_block_num;
-                    picopass_worker->callback(
-                        PicopassWorkerEventLoclassGotMac, picopass_worker->context);
-                } else if((loclass_mode) && !loclass_got_std_key && emu_ctx.loclass_got_std_key) {
-                    loclass_got_std_key = true;
-                    picopass_worker->callback(
-                        PicopassWorkerEventLoclassGotStandardKey, picopass_worker->context);
-                } else {
-                    picopass_worker->callback(
-                        PicopassWorkerEventSuccess, picopass_worker->context);
-                }
-            }
-        }
-        furi_delay_us(1);
-    }
-
-    if(emu_ctx.loclass_writer) {
-        loclass_writer_write_start_stop(emu_ctx.loclass_writer, false);
-        loclass_writer_free(emu_ctx.loclass_writer);
-    }
-
-    nfcv_emu_deinit(nfcv_data);
-    free(nfcv_data);
-}

+ 0 - 57
picopass_worker.h

@@ -1,57 +0,0 @@
-#pragma once
-
-#include "picopass_device.h"
-#include "picopass_keys.h"
-
-typedef struct PicopassWorker PicopassWorker;
-
-typedef enum {
-    // Init states
-    PicopassWorkerStateNone,
-    PicopassWorkerStateBroken,
-    PicopassWorkerStateReady,
-    // Main worker states
-    PicopassWorkerStateDetect,
-    PicopassWorkerStateWrite,
-    PicopassWorkerStateWriteKey,
-    PicopassWorkerStateEliteDictAttack,
-    PicopassWorkerStateEmulate,
-    PicopassWorkerStateLoclass,
-    // Transition
-    PicopassWorkerStateStop,
-} PicopassWorkerState;
-
-typedef enum {
-    // Reserve first 50 events for application events
-    PicopassWorkerEventReserved = 50,
-
-    // Picopass worker common events
-    PicopassWorkerEventSuccess,
-    PicopassWorkerEventFail,
-    PicopassWorkerEventNoCardDetected,
-    PicopassWorkerEventSeEnabled,
-    PicopassWorkerEventAborted,
-    PicopassWorkerEventCardDetected,
-    PicopassWorkerEventNewDictKeyBatch,
-    PicopassWorkerEventNoDictFound,
-    PicopassWorkerEventLoclassGotMac,
-    PicopassWorkerEventLoclassGotStandardKey,
-    PicopassWorkerEventLoclassFileError,
-} PicopassWorkerEvent;
-
-typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
-
-PicopassWorker* picopass_worker_alloc();
-
-PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker);
-
-void picopass_worker_free(PicopassWorker* picopass_worker);
-
-void picopass_worker_start(
-    PicopassWorker* picopass_worker,
-    PicopassWorkerState state,
-    PicopassDeviceData* dev_data,
-    PicopassWorkerCallback callback,
-    void* context);
-
-void picopass_worker_stop(PicopassWorker* picopass_worker);

+ 0 - 36
picopass_worker_i.h

@@ -1,36 +0,0 @@
-#pragma once
-
-#include "picopass_worker.h"
-#include "loclass_writer.h"
-#include "picopass_i.h"
-
-#include <furi.h>
-#include <lib/toolbox/stream/file_stream.h>
-
-#include <furi_hal.h>
-
-#include <stdlib.h>
-#include <rfal_rf.h>
-
-#include <platform.h>
-
-struct PicopassWorker {
-    FuriThread* thread;
-    Storage* storage;
-    Stream* dict_stream;
-
-    PicopassDeviceData* dev_data;
-    PicopassWorkerCallback callback;
-    void* context;
-
-    PicopassWorkerState state;
-};
-
-void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state);
-
-int32_t picopass_worker_task(void* context);
-
-void picopass_worker_detect(PicopassWorker* picopass_worker);
-void picopass_worker_write(PicopassWorker* picopass_worker);
-void picopass_worker_write_key(PicopassWorker* picopass_worker);
-void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode);

+ 667 - 0
protocol/picopass_listener.c

@@ -0,0 +1,667 @@
+#include "picopass_listener_i.h"
+#include "picopass_keys.h"
+
+#include <furi/furi.h>
+
+#define PICOPASS_LISTENER_HAS_MASK(x, b) ((x & b) == b)
+
+typedef enum {
+    PicopassListenerCommandProcessed,
+    PicopassListenerCommandSilent,
+    PicopassListenerCommandSendSoF,
+    PicopassListenerCommandStop,
+} PicopassListenerCommand;
+
+typedef PicopassListenerCommand (
+    *PicopassListenerCommandHandler)(PicopassListener* instance, BitBuffer* buf);
+
+typedef struct {
+    uint8_t start_byte_cmd;
+    size_t cmd_len_bits;
+    PicopassListenerCommandHandler handler;
+} PicopassListenerCmd;
+
+// CSNs from Proxmark3 repo
+static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = {
+    {0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0},
+    {0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0},
+    {0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0},
+    {0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0},
+};
+
+static void picopass_listener_reset(PicopassListener* instance) {
+    instance->state = PicopassListenerStateIdle;
+}
+
+static void picopass_listener_loclass_update_csn(PicopassListener* instance) {
+    // collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
+    const uint8_t* csn =
+        loclass_csns[(instance->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
+    memcpy(instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn, sizeof(PicopassBlock));
+
+    uint8_t key[PICOPASS_BLOCK_LEN] = {};
+    loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
+    memcpy(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, key, sizeof(PicopassBlock));
+
+    picopass_listener_init_cipher_state_key(instance, key);
+}
+
+PicopassListenerCommand
+    picopass_listener_actall_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    if(instance->state != PicopassListenerStateHalt) {
+        instance->state = PicopassListenerStateActive;
+    }
+    // nfc_set_fdt_listen_fc(instance->nfc, 1000);
+
+    return PicopassListenerCommandSendSoF;
+}
+
+PicopassListenerCommand picopass_listener_act_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    PicopassListenerCommand command = PicopassListenerCommandSendSoF;
+
+    if(instance->state != PicopassListenerStateActive) {
+        command = PicopassListenerCommandSilent;
+    }
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_halt_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    PicopassListenerCommand command = PicopassListenerCommandSendSoF;
+
+    // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
+    instance->state = PicopassListenerStateIdle;
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_identify_handler(PicopassListener* instance, BitBuffer* buf) {
+    UNUSED(buf);
+
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateActive) break;
+        picopass_listener_write_anticoll_csn(instance, instance->tx_buffer);
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Error sending CSN: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_select_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if((instance->state == PicopassListenerStateHalt) ||
+           (instance->state == PicopassListenerStateIdle)) {
+            bit_buffer_copy_bytes(
+                instance->tmp_buffer,
+                instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+                sizeof(PicopassBlock));
+        } else {
+            picopass_listener_write_anticoll_csn(instance, instance->tmp_buffer);
+        }
+        const uint8_t* listener_uid = bit_buffer_get_data(instance->tmp_buffer);
+        const uint8_t* received_data = bit_buffer_get_data(buf);
+
+        if(memcmp(listener_uid, &received_data[1], PICOPASS_BLOCK_LEN) != 0) {
+            if(instance->state == PicopassListenerStateActive) {
+                instance->state = PicopassListenerStateIdle;
+            } else if(instance->state == PicopassListenerStateSelected) {
+                // Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
+                instance->state = PicopassListenerStateIdle;
+            }
+            break;
+        }
+
+        instance->state = PicopassListenerStateSelected;
+        bit_buffer_copy_bytes(
+            instance->tx_buffer,
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+            sizeof(PicopassBlock));
+
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Error sending select response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_read_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        uint8_t block_num = bit_buffer_get_byte(buf, 1);
+        if(block_num > PICOPASS_MAX_APP_LIMIT) break;
+
+        bit_buffer_reset(instance->tx_buffer);
+        if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
+           (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+            for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+                bit_buffer_append_byte(instance->tx_buffer, 0xff);
+            }
+        } else {
+            bit_buffer_copy_bytes(
+                instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
+        }
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx read block response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+static PicopassListenerCommand
+    picopass_listener_readcheck(PicopassListener* instance, BitBuffer* buf, uint8_t key_block_num) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateSelected) break;
+        uint8_t block_num = bit_buffer_get_byte(buf, 1);
+        if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break;
+
+        // loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
+        // we can also no-op if the key block is the same, CHECK re-inits if it failed already
+        if((instance->key_block_num != key_block_num) &&
+           (instance->mode != PicopassListenerModeLoclass)) {
+            instance->key_block_num = key_block_num;
+            picopass_listener_init_cipher_state(instance);
+        }
+
+        // DATA(8)
+        bit_buffer_copy_bytes(
+            instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
+        NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
+        if(error != NfcErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx read check response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_readcheck_kd_handler(PicopassListener* instance, BitBuffer* buf) {
+    return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KD_BLOCK_INDEX);
+}
+
+PicopassListenerCommand
+    picopass_listener_readcheck_kc_handler(PicopassListener* instance, BitBuffer* buf) {
+    return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KC_BLOCK_INDEX);
+}
+
+PicopassListenerCommand
+    picopass_listener_check_handler_loclass(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+    NfcCommand callback_command = NfcCommandContinue;
+
+    // LOCLASS Reader attack mode
+    do {
+#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
+        // loclass mode stores the derived standard debit key in Kd to check
+
+        PicopassBlock key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX];
+        uint8_t rmac[4];
+        uint8_t rx_data[9] = {};
+        bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
+        loclass_opt_doReaderMAC_2(instance->cipher_state, &rx_data[1], rmac, key.data);
+
+        if(!memcmp(&rx_data[5], rmac, 4)) {
+            // MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
+            // Either way no point logging it.
+
+            FURI_LOG_W(TAG, "loclass: standard key detected during collection");
+            if(instance->callback) {
+                instance->event.type = PicopassListenerEventTypeLoclassGotStandardKey;
+                callback_command = instance->callback(instance->event, instance->context);
+                if(callback_command == NfcCommandStop) {
+                    command = PicopassListenerCommandStop;
+                }
+            }
+
+            // Don't reset the state as the reader may try a different key next without going through anticoll
+            // The reader is always free to redo the anticoll if it wants to anyway
+
+            break;
+        }
+#endif
+
+        // Save to buffer to defer flushing when we rotate CSN
+        memcpy(
+            instance->loclass_mac_buffer + ((instance->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
+            &rx_data[1],
+            8);
+
+        // Rotate to the next CSN/attempt
+        instance->key_block_num++;
+
+        // CSN changed
+        if(instance->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
+            // Flush NR-MACs for this CSN to SD card
+            for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
+                loclass_writer_write_params(
+                    instance->writer,
+                    instance->key_block_num + i - LOCLASS_NUM_PER_CSN,
+                    instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+                    instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
+                    instance->loclass_mac_buffer + (i * 8),
+                    instance->loclass_mac_buffer + (i * 8) + 4);
+            }
+
+            if(instance->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
+                picopass_listener_loclass_update_csn(instance);
+                // Only reset the state when we change to a new CSN for the same reason as when we get a standard key
+                instance->state = PicopassListenerStateIdle;
+            }
+        }
+        if(instance->callback) {
+            instance->event.type = PicopassListenerEventTypeLoclassGotMac;
+            instance->callback(instance->event, instance->context);
+        }
+
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        uint8_t rmac[4] = {};
+        uint8_t tmac[4] = {};
+        const uint8_t* key = instance->data->AA1[instance->key_block_num].data;
+        // Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
+        uint8_t rx_data[9] = {};
+        bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
+        loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
+
+#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
+        if(memcmp(&rx_data[5], rmac, 4)) {
+            // Bad MAC from reader, do not send a response.
+            FURI_LOG_I(TAG, "Got bad MAC from reader");
+            // Reset the cipher state since we don't do it in READCHECK
+            picopass_listener_init_cipher_state(instance);
+            break;
+        }
+#endif
+
+        bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
+        NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
+        if(error != NfcErrorNone) {
+            FURI_LOG_D(TAG, "Failed tx update response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+PicopassListenerCommand
+    picopass_listener_check_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateSelected) break;
+        if(instance->mode == PicopassListenerModeLoclass) {
+            command = picopass_listener_check_handler_loclass(instance, buf);
+        } else {
+            command = picopass_listener_check_handler_emulation(instance, buf);
+        }
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_update_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->mode == PicopassListenerModeLoclass) break;
+        if(instance->state != PicopassListenerStateSelected) break;
+
+        PicopassBlock config_block = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX];
+        bool pers_mode = PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_PERS);
+
+        const uint8_t* rx_data = bit_buffer_get_data(buf);
+        uint8_t block_num = rx_data[1];
+        if(block_num == PICOPASS_CSN_BLOCK_INDEX) break; // CSN is always read only
+        if(!pers_mode && PICOPASS_LISTENER_HAS_MASK(config_block.data[3], 0x80))
+            break; // Chip is in RO mode, no updated possible (even ePurse)
+        if(!pers_mode && (block_num == PICOPASS_SECURE_AIA_BLOCK_INDEX))
+            break; // AIA can only be set in personalisation mode
+        if(!pers_mode &&
+           ((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX ||
+             block_num == PICOPASS_SECURE_KC_BLOCK_INDEX) &&
+            (!PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_CRYPT10))))
+            break;
+
+        if(block_num >= 6 && block_num <= 12) {
+            // bit0 is block6, up to bit6 being block12
+            if(!PICOPASS_LISTENER_HAS_MASK(config_block.data[3], (1 << (block_num - 6)))) {
+                // Block is marked as read-only, deny writing
+                break;
+            }
+        }
+        // TODO: Check CRC/SIGN depending on if in secure mode
+        // Check correct key
+        // -> Kd only allows decrementing e-Purse
+        // -> per-app controlled by key access config
+        // bool keyAccess = PICOPASS_LISTENER_HAS_MASK(config_block.data[5], 0x01);
+        // -> must auth with that key to change it
+
+        PicopassBlock new_block = {};
+        switch(block_num) {
+        case PICOPASS_CONFIG_BLOCK_INDEX:
+            new_block.data[0] = config_block.data[0]; // Applications Limit
+            new_block.data[1] = config_block.data[1] & rx_data[3]; // OTP
+            new_block.data[2] = config_block.data[2] & rx_data[4]; // OTP
+            new_block.data[3] = config_block.data[3] & rx_data[5]; // Block Write Lock
+            new_block.data[4] = config_block.data[4]; // Chip Config
+            new_block.data[5] = config_block.data[5]; // Memory Config
+            new_block.data[6] = rx_data[8]; // EAS
+            new_block.data[7] = config_block.data[7]; // Fuses
+
+            // Some parts allow w (but not e) if in persMode
+            if(pers_mode) {
+                new_block.data[0] &= rx_data[2]; // Applications Limit
+                new_block.data[4] &= rx_data[6]; // Chip Config
+                new_block.data[5] &= rx_data[7]; // Memory Config
+                new_block.data[7] &= rx_data[9]; // Fuses
+            } else {
+                // Fuses allows setting Crypt1/0 from 1 to 0 only during application mode
+                new_block.data[7] &= rx_data[9] | ~PICOPASS_FUSE_CRYPT10;
+            }
+            break;
+
+        case PICOPASS_SECURE_EPURSE_BLOCK_INDEX:
+            // ePurse updates swap first and second half of the block each update
+            memcpy(&new_block.data[4], &rx_data[2], 4);
+            memcpy(&new_block.data[0], &rx_data[6], 4);
+            break;
+
+        case PICOPASS_SECURE_KD_BLOCK_INDEX:
+            // fallthrough
+        case PICOPASS_SECURE_KC_BLOCK_INDEX:
+            if(!pers_mode) {
+                new_block = instance->data->AA1[block_num];
+                for(size_t i = 0; i < sizeof(PicopassBlock); i++) {
+                    new_block.data[i] ^= rx_data[i + 2];
+                }
+                break;
+            }
+            // Use default case when in personalisation mode
+            // fallthrough
+        default:
+            memcpy(new_block.data, &rx_data[2], sizeof(PicopassBlock));
+            break;
+        }
+
+        instance->data->AA1[block_num] = new_block;
+        if((block_num == instance->key_block_num) ||
+           (block_num == PICOPASS_SECURE_EPURSE_BLOCK_INDEX)) {
+            picopass_listener_init_cipher_state(instance);
+        }
+
+        bit_buffer_reset(instance->tx_buffer);
+        if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
+           (block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+            // Key updates always return FF's
+            for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+                bit_buffer_append_byte(instance->tx_buffer, 0xff);
+            }
+        } else {
+            bit_buffer_copy_bytes(
+                instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
+        }
+
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx update response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+PicopassListenerCommand
+    picopass_listener_read4_handler(PicopassListener* instance, BitBuffer* buf) {
+    PicopassListenerCommand command = PicopassListenerCommandSilent;
+
+    do {
+        if(instance->state != PicopassListenerStateSelected) break;
+
+        uint8_t block_start = bit_buffer_get_byte(buf, 1);
+        if(block_start + 4 >= PICOPASS_MAX_APP_LIMIT) break;
+
+        // TODO: Check CRC?
+        // TODO: Check auth?
+
+        bit_buffer_reset(instance->tx_buffer);
+        for(uint8_t i = block_start; i < block_start + 4; i++) {
+            if((i == PICOPASS_SECURE_KD_BLOCK_INDEX) || (i == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
+                for(size_t j = 0; j < sizeof(PicopassBlock); j++) {
+                    bit_buffer_append_byte(instance->tx_buffer, 0xff);
+                }
+            } else {
+                bit_buffer_append_bytes(
+                    instance->tx_buffer, instance->data->AA1[i].data, sizeof(PicopassBlock));
+            }
+        }
+
+        PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_D(TAG, "Failed to tx read4 response: %d", error);
+            break;
+        }
+
+        command = PicopassListenerCommandProcessed;
+    } while(false);
+
+    return command;
+}
+
+static const PicopassListenerCmd picopass_listener_cmd_handlers[] = {
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_ACTALL,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_actall_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_ACT,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_act_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_HALT,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_halt_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
+        .cmd_len_bits = 8,
+        .handler = picopass_listener_identify_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_SELECT,
+        .cmd_len_bits = 8 * 9,
+        .handler = picopass_listener_select_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
+        .cmd_len_bits = 8 * 4,
+        .handler = picopass_listener_read_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KD,
+        .cmd_len_bits = 8 * 2,
+        .handler = picopass_listener_readcheck_kd_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KC,
+        .cmd_len_bits = 8 * 2,
+        .handler = picopass_listener_readcheck_kc_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_CHECK,
+        .cmd_len_bits = 8 * 9,
+        .handler = picopass_listener_check_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_UPDATE,
+        .cmd_len_bits = 8 * 14,
+        .handler = picopass_listener_update_handler,
+    },
+    {
+        .start_byte_cmd = RFAL_PICOPASS_CMD_READ4,
+        .cmd_len_bits = 8 * 4,
+        .handler = picopass_listener_read4_handler,
+    },
+};
+
+PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data) {
+    furi_assert(nfc);
+    furi_assert(data);
+
+    PicopassListener* instance = malloc(sizeof(PicopassListener));
+    instance->nfc = nfc;
+    instance->data = malloc(sizeof(PicopassDeviceData));
+    mempcpy(instance->data, data, sizeof(PicopassDeviceData));
+
+    instance->tx_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
+    instance->tmp_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
+
+    nfc_set_fdt_listen_fc(instance->nfc, PICOPASS_FDT_LISTEN_FC);
+    nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693);
+
+    return instance;
+}
+
+void picopass_listener_free(PicopassListener* instance) {
+    furi_assert(instance);
+
+    bit_buffer_free(instance->tx_buffer);
+    bit_buffer_free(instance->tmp_buffer);
+    free(instance->data);
+    if(instance->writer) {
+        loclass_writer_write_start_stop(instance->writer, false);
+        loclass_writer_free(instance->writer);
+    }
+    free(instance);
+}
+
+bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode) {
+    furi_assert(instance);
+    bool success = true;
+
+    instance->mode = mode;
+    if(instance->mode == PicopassListenerModeLoclass) {
+        instance->key_block_num = 0;
+        picopass_listener_loclass_update_csn(instance);
+        instance->writer = loclass_writer_alloc();
+        if(instance->writer) {
+            loclass_writer_write_start_stop(instance->writer, true);
+        } else {
+            success = false;
+        }
+    }
+
+    return success;
+}
+
+NfcCommand picopass_listener_start_callback(NfcEvent event, void* context) {
+    furi_assert(context);
+
+    NfcCommand command = NfcCommandContinue;
+    PicopassListener* instance = context;
+    BitBuffer* rx_buf = event.data.buffer;
+
+    PicopassListenerCommand picopass_cmd = PicopassListenerCommandSilent;
+    if(event.type == NfcEventTypeRxEnd) {
+        for(size_t i = 0; i < COUNT_OF(picopass_listener_cmd_handlers); i++) {
+            if(bit_buffer_get_size(rx_buf) != picopass_listener_cmd_handlers[i].cmd_len_bits) {
+                continue;
+            }
+            if(bit_buffer_get_byte(rx_buf, 0) !=
+               picopass_listener_cmd_handlers[i].start_byte_cmd) {
+                continue;
+            }
+            picopass_cmd = picopass_listener_cmd_handlers[i].handler(instance, rx_buf);
+            break;
+        }
+        if(picopass_cmd == PicopassListenerCommandSendSoF) {
+            nfc_iso15693_listener_tx_sof(instance->nfc);
+        } else if(picopass_cmd == PicopassListenerCommandStop) {
+            command = NfcCommandStop;
+        }
+    }
+
+    return command;
+}
+
+void picopass_listener_start(
+    PicopassListener* instance,
+    PicopassListenerCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    picopass_listener_reset(instance);
+    nfc_start(instance->nfc, picopass_listener_start_callback, instance);
+}
+
+void picopass_listener_stop(PicopassListener* instance) {
+    furi_assert(instance);
+
+    nfc_stop(instance->nfc);
+}
+
+const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance) {
+    furi_assert(instance);
+
+    return instance->data;
+}

+ 45 - 0
protocol/picopass_listener.h

@@ -0,0 +1,45 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include "picopass_protocol.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    PicopassListenerModeEmulation,
+    PicopassListenerModeLoclass,
+} PicopassListenerMode;
+
+typedef enum {
+    PicopassListenerEventTypeLoclassGotStandardKey,
+    PicopassListenerEventTypeLoclassGotMac,
+} PicopassListenerEventType;
+
+typedef struct {
+    PicopassListenerEventType type;
+} PicopassListenerEvent;
+
+typedef NfcCommand (*PicopassListenerCallback)(PicopassListenerEvent event, void* context);
+
+typedef struct PicopassListener PicopassListener;
+
+PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data);
+
+void picopass_listener_free(PicopassListener* instance);
+
+bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode);
+
+void picopass_listener_start(
+    PicopassListener* instance,
+    PicopassListenerCallback callback,
+    void* context);
+
+void picopass_listener_stop(PicopassListener* instance);
+
+const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 52 - 0
protocol/picopass_listener_i.c

@@ -0,0 +1,52 @@
+#include "picopass_listener_i.h"
+
+#include <furi/furi.h>
+
+static PicopassError picopass_listener_process_error(NfcError error) {
+    PicopassError ret = PicopassErrorNone;
+
+    switch(error) {
+    case NfcErrorNone:
+        ret = PicopassErrorNone;
+        break;
+
+    default:
+        ret = PicopassErrorTimeout;
+        break;
+    }
+
+    return ret;
+}
+
+void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key) {
+    uint8_t cc[PICOPASS_BLOCK_LEN] = {};
+    memcpy(
+        cc, instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, sizeof(PicopassBlock));
+
+    instance->cipher_state = loclass_opt_doTagMAC_1(cc, key);
+}
+
+void picopass_listener_init_cipher_state(PicopassListener* instance) {
+    uint8_t key[PICOPASS_BLOCK_LEN] = {};
+    memcpy(key, instance->data->AA1[instance->key_block_num].data, sizeof(PicopassBlock));
+
+    picopass_listener_init_cipher_state_key(instance, key);
+}
+
+PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer) {
+    iso13239_crc_append(Iso13239CrcTypePicopass, tx_buffer);
+    NfcError error = nfc_listener_tx(instance->nfc, tx_buffer);
+
+    return picopass_listener_process_error(error);
+}
+
+// from proxmark3 armsrc/iclass.c rotateCSN
+PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer) {
+    const uint8_t* uid = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+    bit_buffer_reset(buffer);
+    for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+        bit_buffer_append_byte(buffer, (uid[i] >> 3) | (uid[(i + 1) % 8] << 5));
+    }
+
+    return PicopassErrorNone;
+}

+ 47 - 0
protocol/picopass_listener_i.h

@@ -0,0 +1,47 @@
+#pragma once
+
+#include "picopass_listener.h"
+#include <nfc/helpers/iso13239_crc.h>
+
+#include <optimized_ikeys.h>
+#include <optimized_cipher.h>
+#include <loclass_writer.h>
+
+#define TAG "PicopassListener"
+
+#define PICOPASS_LISTENER_BUFFER_SIZE_MAX (255)
+
+typedef enum {
+    PicopassListenerStateIdle,
+    PicopassListenerStateHalt,
+    PicopassListenerStateActive,
+    PicopassListenerStateSelected,
+} PicopassListenerState;
+
+struct PicopassListener {
+    Nfc* nfc;
+    PicopassDeviceData* data;
+    PicopassListenerState state;
+
+    LoclassState_t cipher_state;
+    PicopassListenerMode mode;
+
+    BitBuffer* tx_buffer;
+    BitBuffer* tmp_buffer;
+    uint8_t key_block_num;
+
+    LoclassWriter* writer;
+    uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
+
+    PicopassListenerEvent event;
+    PicopassListenerCallback callback;
+    void* context;
+};
+
+void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key);
+
+void picopass_listener_init_cipher_state(PicopassListener* instance);
+
+PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer);
+
+PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer);

+ 543 - 0
protocol/picopass_poller.c

@@ -0,0 +1,543 @@
+#include "picopass_poller_i.h"
+
+#include "../loclass/optimized_cipher.h"
+
+#include <furi/furi.h>
+
+#define TAG "Picopass"
+
+typedef NfcCommand (*PicopassPollerStateHandler)(PicopassPoller* instance);
+
+static void picopass_poller_reset(PicopassPoller* instance) {
+    instance->current_block = 0;
+}
+
+static void picopass_poller_prepare_read(PicopassPoller* instance) {
+    instance->app_limit = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] <
+                                  PICOPASS_MAX_APP_LIMIT ?
+                              instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
+                              PICOPASS_MAX_APP_LIMIT;
+    instance->current_block = 2;
+}
+
+NfcCommand picopass_poller_request_mode_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->event.type = PicopassPollerEventTypeRequestMode;
+    command = instance->callback(instance->event, instance->context);
+    instance->mode = instance->event_data.req_mode.mode;
+    instance->state = PicopassPollerStateDetect;
+
+    return command;
+}
+
+NfcCommand picopass_poller_detect_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = picopass_poller_actall(instance);
+
+    if(error == PicopassErrorNone) {
+        instance->state = PicopassPollerStateSelect;
+        instance->event.type = PicopassPollerEventTypeCardDetected;
+        command = instance->callback(instance->event, instance->context);
+    } else {
+        furi_delay_ms(100);
+    }
+
+    return command;
+}
+
+NfcCommand picopass_poller_select_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        PicopassError error = picopass_poller_identify(instance, &instance->col_res_serial_num);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+        error =
+            picopass_poller_select(instance, &instance->col_res_serial_num, &instance->serial_num);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+        if(instance->mode == PicopassPollerModeRead) {
+            instance->state = PicopassPollerStatePreAuth;
+        } else {
+            instance->state = PicopassPollerStateAuth;
+        }
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = PicopassErrorNone;
+
+    do {
+        memcpy(
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
+            instance->serial_num.data,
+            sizeof(PicopassSerialNum));
+        FURI_LOG_D(
+            TAG,
+            "csn %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
+
+        PicopassBlock block = {};
+        error = picopass_poller_read_block(instance, 1, &block);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        memcpy(
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data,
+            block.data,
+            sizeof(PicopassBlock));
+        FURI_LOG_D(
+            TAG,
+            "config %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
+
+        error = picopass_poller_read_block(instance, 5, &block);
+        if(error != PicopassErrorNone) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        memcpy(
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
+            block.data,
+            sizeof(PicopassBlock));
+        FURI_LOG_D(
+            TAG,
+            "aia %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6],
+            instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]);
+
+        instance->state = PicopassPollerStateCheckSecurity;
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    // Thank you proxmark!
+    PicopassBlock temp_block = {};
+    memset(temp_block.data, 0xff, sizeof(PicopassBlock));
+    instance->data->pacs.legacy =
+        (memcmp(
+             instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
+             temp_block.data,
+             sizeof(PicopassBlock)) == 0);
+
+    temp_block.data[3] = 0x00;
+    temp_block.data[4] = 0x06;
+    instance->data->pacs.se_enabled =
+        (memcmp(
+             instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
+             temp_block.data,
+             sizeof(PicopassBlock)) == 0);
+
+    if(instance->data->pacs.se_enabled) {
+        FURI_LOG_D(TAG, "SE enabled");
+        instance->state = PicopassPollerStateFail;
+    } else {
+        instance->state = PicopassPollerStateAuth;
+    }
+
+    return command;
+}
+
+NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        // Request key
+        instance->event.type = PicopassPollerEventTypeRequestKey;
+        command = instance->callback(instance->event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        if(!instance->event_data.req_key.is_key_provided) {
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+        FURI_LOG_D(
+            TAG,
+            "Try to %s auth with key %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->event_data.req_key.is_elite_key ? "elite" : "standard",
+            instance->event_data.req_key.key[0],
+            instance->event_data.req_key.key[1],
+            instance->event_data.req_key.key[2],
+            instance->event_data.req_key.key[3],
+            instance->event_data.req_key.key[4],
+            instance->event_data.req_key.key[5],
+            instance->event_data.req_key.key[6],
+            instance->event_data.req_key.key[7]);
+
+        PicopassReadCheckResp read_check_resp = {};
+        uint8_t* csn = instance->serial_num.data;
+        memset(instance->div_key, 0, sizeof(instance->div_key));
+        uint8_t* div_key = NULL;
+
+        if(instance->mode == PicopassPollerModeRead) {
+            div_key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
+        } else {
+            div_key = instance->div_key;
+        }
+
+        uint8_t ccnr[12] = {};
+        PicopassMac mac = {};
+
+        PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
+        if(error == PicopassErrorTimeout) {
+            instance->event.type = PicopassPollerEventTypeCardLost;
+            command = instance->callback(instance->event, instance->context);
+            instance->state = PicopassPollerStateDetect;
+            break;
+        } else if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Read check failed: %d", error);
+            break;
+        }
+        memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0
+
+        loclass_iclass_calc_div_key(
+            csn,
+            instance->event_data.req_key.key,
+            div_key,
+            instance->event_data.req_key.is_elite_key);
+        loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
+
+        PicopassCheckResp check_resp = {};
+        error = picopass_poller_check(instance, &mac, &check_resp);
+        if(error == PicopassErrorNone) {
+            FURI_LOG_I(TAG, "Found key");
+            memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
+            if(instance->mode == PicopassPollerModeRead) {
+                memcpy(
+                    instance->data->pacs.key, instance->event_data.req_key.key, PICOPASS_KEY_LEN);
+                instance->data->pacs.elite_kdf = instance->event_data.req_key.is_elite_key;
+                picopass_poller_prepare_read(instance);
+                instance->state = PicopassPollerStateReadBlock;
+            } else if(instance->mode == PicopassPollerModeWrite) {
+                instance->state = PicopassPollerStateWriteBlock;
+            } else {
+                instance->state = PicopassPollerStateWriteKey;
+            }
+        }
+
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    do {
+        if(instance->current_block == instance->app_limit) {
+            instance->state = PicopassPollerStateParseCredential;
+            break;
+        }
+
+        if(instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
+            // Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
+            instance->current_block++;
+        }
+
+        PicopassBlock block = {};
+        PicopassError error =
+            picopass_poller_read_block(instance, instance->current_block, &block);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Failed to read block %d: %d", instance->current_block, error);
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        FURI_LOG_D(
+            TAG,
+            "Block %d: %02x%02x%02x%02x%02x%02x%02x%02x",
+            instance->current_block,
+            block.data[0],
+            block.data[1],
+            block.data[2],
+            block.data[3],
+            block.data[4],
+            block.data[5],
+            block.data[6],
+            block.data[7]);
+        memcpy(
+            instance->data->AA1[instance->current_block].data, block.data, sizeof(PicopassBlock));
+        instance->current_block++;
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    picopass_device_parse_credential(instance->data->AA1, &instance->data->pacs);
+    instance->state = PicopassPollerStateParseWiegand;
+    return command;
+}
+
+NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs);
+    instance->state = PicopassPollerStateSuccess;
+    return command;
+}
+
+NfcCommand picopass_poller_write_block_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = PicopassErrorNone;
+
+    do {
+        instance->event.type = PicopassPollerEventTypeRequestWriteBlock;
+        command = instance->callback(instance->event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        PicopassPollerEventDataRequestWriteBlock* write_block = &instance->event_data.req_write;
+        if(!write_block->perform_write) {
+            instance->state = PicopassPollerStateSuccess;
+            break;
+        }
+
+        FURI_LOG_D(TAG, "Writing %d block", write_block->block_num);
+        uint8_t data[9] = {};
+        data[0] = write_block->block_num;
+        memcpy(&data[1], write_block->block->data, PICOPASS_BLOCK_LEN);
+        loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
+        FURI_LOG_D(
+            TAG,
+            "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
+            write_block->block_num,
+            data[1],
+            data[2],
+            data[3],
+            data[4],
+            data[5],
+            data[6],
+            data[7],
+            data[8],
+            instance->mac.data[0],
+            instance->mac.data[1],
+            instance->mac.data[2],
+            instance->mac.data[3]);
+        error = picopass_poller_write_block(
+            instance, write_block->block_num, write_block->block, &instance->mac);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_E(TAG, "Failed to write block %d. Error %d", write_block->block_num, error);
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_write_key_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+    PicopassError error = PicopassErrorNone;
+
+    do {
+        instance->event.type = PicopassPollerEventTypeRequestWriteKey;
+        command = instance->callback(instance->event, instance->context);
+        if(command != NfcCommandContinue) break;
+
+        const PicopassDeviceData* picopass_data = instance->event_data.req_write_key.data;
+        const uint8_t* new_key = instance->event_data.req_write_key.key;
+        bool is_elite_key = instance->event_data.req_write_key.is_elite_key;
+
+        const uint8_t* csn = picopass_data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
+        const uint8_t* config_block = picopass_data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
+        uint8_t fuses = config_block[7];
+        const uint8_t* old_key = picopass_data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
+
+        PicopassBlock new_block = {};
+        loclass_iclass_calc_div_key(csn, new_key, new_block.data, is_elite_key);
+
+        if((fuses & 0x80) == 0x80) {
+            FURI_LOG_D(TAG, "Plain write for personalized mode key change");
+        } else {
+            FURI_LOG_D(TAG, "XOR write for application mode key change");
+            // XOR when in application mode
+            for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+                new_block.data[i] ^= old_key[i];
+            }
+        }
+
+        FURI_LOG_D(TAG, "Writing key to %d block", PICOPASS_SECURE_KD_BLOCK_INDEX);
+        uint8_t data[9] = {};
+        data[0] = PICOPASS_SECURE_KD_BLOCK_INDEX;
+        memcpy(&data[1], new_block.data, PICOPASS_BLOCK_LEN);
+        loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
+        FURI_LOG_D(
+            TAG,
+            "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
+            PICOPASS_SECURE_KD_BLOCK_INDEX,
+            data[1],
+            data[2],
+            data[3],
+            data[4],
+            data[5],
+            data[6],
+            data[7],
+            data[8],
+            instance->mac.data[0],
+            instance->mac.data[1],
+            instance->mac.data[2],
+            instance->mac.data[3]);
+        error = picopass_poller_write_block(
+            instance, PICOPASS_SECURE_KD_BLOCK_INDEX, &new_block, &instance->mac);
+        if(error != PicopassErrorNone) {
+            FURI_LOG_E(
+                TAG, "Failed to write block %d. Error %d", PICOPASS_SECURE_KD_BLOCK_INDEX, error);
+            instance->state = PicopassPollerStateFail;
+            break;
+        }
+        instance->state = PicopassPollerStateSuccess;
+
+    } while(false);
+
+    return command;
+}
+
+NfcCommand picopass_poller_success_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandContinue;
+
+    instance->event.type = PicopassPollerEventTypeSuccess;
+    command = instance->callback(instance->event, instance->context);
+    furi_delay_ms(100);
+
+    return command;
+}
+
+NfcCommand picopass_poller_fail_handler(PicopassPoller* instance) {
+    NfcCommand command = NfcCommandReset;
+
+    instance->event.type = PicopassPollerEventTypeFail;
+    command = instance->callback(instance->event, instance->context);
+    picopass_poller_reset(instance);
+    instance->state = PicopassPollerStateDetect;
+
+    return command;
+}
+
+static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPollerStateNum] = {
+    [PicopassPollerStateRequestMode] = picopass_poller_request_mode_handler,
+    [PicopassPollerStateDetect] = picopass_poller_detect_handler,
+    [PicopassPollerStateSelect] = picopass_poller_select_handler,
+    [PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
+    [PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
+    [PicopassPollerStateAuth] = picopass_poller_auth_handler,
+    [PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
+    [PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,
+    [PicopassPollerStateWriteKey] = picopass_poller_write_key_handler,
+    [PicopassPollerStateParseCredential] = picopass_poller_parse_credential_handler,
+    [PicopassPollerStateParseWiegand] = picopass_poller_parse_wiegand_handler,
+    [PicopassPollerStateSuccess] = picopass_poller_success_handler,
+    [PicopassPollerStateFail] = picopass_poller_fail_handler,
+};
+
+static NfcCommand picopass_poller_callback(NfcEvent event, void* context) {
+    furi_assert(context);
+
+    PicopassPoller* instance = context;
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == NfcEventTypePollerReady) {
+        command = picopass_poller_state_handler[instance->state](instance);
+    }
+
+    if(instance->session_state == PicopassPollerSessionStateStopRequest) {
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+void picopass_poller_start(
+    PicopassPoller* instance,
+    PicopassPollerCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(instance->session_state == PicopassPollerSessionStateIdle);
+
+    instance->callback = callback;
+    instance->context = context;
+
+    instance->session_state = PicopassPollerSessionStateActive;
+    nfc_start(instance->nfc, picopass_poller_callback, instance);
+}
+
+void picopass_poller_stop(PicopassPoller* instance) {
+    furi_assert(instance);
+
+    instance->session_state = PicopassPollerSessionStateStopRequest;
+    nfc_stop(instance->nfc);
+    instance->session_state = PicopassPollerSessionStateIdle;
+}
+
+PicopassPoller* picopass_poller_alloc(Nfc* nfc) {
+    furi_assert(nfc);
+
+    PicopassPoller* instance = malloc(sizeof(PicopassPoller));
+    instance->nfc = nfc;
+    nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693);
+    nfc_set_guard_time_us(instance->nfc, 10000);
+    nfc_set_fdt_poll_fc(instance->nfc, 5000);
+    nfc_set_fdt_poll_poll_us(instance->nfc, 1000);
+
+    instance->event.data = &instance->event_data;
+    instance->data = malloc(sizeof(PicopassDeviceData));
+
+    instance->tx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
+    instance->rx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
+    instance->tmp_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
+
+    return instance;
+}
+
+void picopass_poller_free(PicopassPoller* instance) {
+    furi_assert(instance);
+
+    free(instance->data);
+    bit_buffer_free(instance->tx_buffer);
+    bit_buffer_free(instance->rx_buffer);
+    bit_buffer_free(instance->tmp_buffer);
+    free(instance);
+}
+
+const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance) {
+    furi_assert(instance);
+
+    return instance->data;
+}

+ 80 - 0
protocol/picopass_poller.h

@@ -0,0 +1,80 @@
+#pragma once
+
+#include <nfc/nfc.h>
+#include "picopass_protocol.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    PicopassPollerEventTypeRequestMode,
+    PicopassPollerEventTypeCardDetected,
+    PicopassPollerEventTypeCardLost,
+    PicopassPollerEventTypeRequestKey,
+    PicopassPollerEventTypeRequestWriteBlock,
+    PicopassPollerEventTypeRequestWriteKey,
+    PicopassPollerEventTypeSuccess,
+    PicopassPollerEventTypeFail,
+} PicopassPollerEventType;
+
+typedef enum {
+    PicopassPollerModeRead,
+    PicopassPollerModeWrite,
+    PicopassPollerModeWriteKey,
+} PicopassPollerMode;
+
+typedef struct {
+    PicopassPollerMode mode;
+} PicopassPollerEventDataRequestMode;
+
+typedef struct {
+    uint8_t key[PICOPASS_KEY_LEN];
+    bool is_key_provided;
+    bool is_elite_key;
+} PicopassPollerEventDataRequestKey;
+
+typedef struct {
+    bool perform_write;
+    uint8_t block_num;
+    const PicopassBlock* block;
+} PicopassPollerEventDataRequestWriteBlock;
+
+typedef struct {
+    const PicopassDeviceData* data;
+    uint8_t key[PICOPASS_KEY_LEN];
+    bool is_elite_key;
+} PicopassPollerEventDataRequestWriteKey;
+
+typedef union {
+    PicopassPollerEventDataRequestMode req_mode;
+    PicopassPollerEventDataRequestKey req_key;
+    PicopassPollerEventDataRequestWriteBlock req_write;
+    PicopassPollerEventDataRequestWriteKey req_write_key;
+} PicopassPollerEventData;
+
+typedef struct {
+    PicopassPollerEventType type;
+    PicopassPollerEventData* data;
+} PicopassPollerEvent;
+
+typedef NfcCommand (*PicopassPollerCallback)(PicopassPollerEvent event, void* context);
+
+typedef struct PicopassPoller PicopassPoller;
+
+PicopassPoller* picopass_poller_alloc(Nfc* nfc);
+
+void picopass_poller_free(PicopassPoller* instance);
+
+void picopass_poller_start(
+    PicopassPoller* instance,
+    PicopassPollerCallback callback,
+    void* context);
+
+void picopass_poller_stop(PicopassPoller* instance);
+
+const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 217 - 0
protocol/picopass_poller_i.c

@@ -0,0 +1,217 @@
+#include "picopass_poller_i.h"
+
+#include <nfc/helpers/iso14443_crc.h>
+
+#define PICOPASS_POLLER_FWT_FC (100000)
+
+#define TAG "Picopass"
+
+static PicopassError picopass_poller_process_error(NfcError error) {
+    PicopassError ret = PicopassErrorNone;
+
+    switch(error) {
+    case NfcErrorNone:
+        ret = PicopassErrorNone;
+        break;
+
+    default:
+        ret = PicopassErrorTimeout;
+        break;
+    }
+
+    return ret;
+}
+
+static PicopassError picopass_poller_send_frame(
+    PicopassPoller* instance,
+    BitBuffer* tx_buffer,
+    BitBuffer* rx_buffer,
+    uint32_t fwt_fc) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt_fc);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+        if(!iso13239_crc_check(Iso13239CrcTypePicopass, rx_buffer)) {
+            ret = PicopassErrorIncorrectCrc;
+            break;
+        }
+        iso13239_crc_trim(instance->rx_buffer);
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_actall(PicopassPoller* instance) {
+    PicopassError ret = PicopassErrorNone;
+
+    bit_buffer_reset(instance->tx_buffer);
+    bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_ACTALL);
+
+    NfcError error = nfc_poller_trx(
+        instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+    if(error != NfcErrorIncompleteFrame) {
+        ret = picopass_poller_process_error(error);
+    }
+
+    return ret;
+}
+
+PicopassError picopass_poller_identify(
+    PicopassPoller* instance,
+    PicopassColResSerialNum* col_res_serial_num) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
+        ret = picopass_poller_send_frame(
+            instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(ret != PicopassErrorNone) break;
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassColResSerialNum)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(
+            instance->rx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_select(
+    PicopassPoller* instance,
+    PicopassColResSerialNum* col_res_serial_num,
+    PicopassSerialNum* serial_num) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_SELECT);
+        bit_buffer_append_bytes(
+            instance->tx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
+        ret = picopass_poller_send_frame(
+            instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(ret != PicopassErrorNone) break;
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassSerialNum)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(instance->rx_buffer, serial_num->data, sizeof(PicopassSerialNum));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError
+    picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tmp_buffer);
+        bit_buffer_append_byte(instance->tmp_buffer, block_num);
+        iso13239_crc_append(Iso13239CrcTypePicopass, instance->tmp_buffer);
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
+        bit_buffer_append(instance->tx_buffer, instance->tmp_buffer);
+
+        ret = picopass_poller_send_frame(
+            instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(ret != PicopassErrorNone) break;
+
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassBlock)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(instance->rx_buffer, block->data, sizeof(PicopassBlock));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError
+    picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READCHECK_KD);
+        bit_buffer_append_byte(instance->tx_buffer, 0x02);
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassReadCheckResp)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(
+            instance->rx_buffer, read_check_resp->data, sizeof(PicopassReadCheckResp));
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_check(
+    PicopassPoller* instance,
+    PicopassMac* mac,
+    PicopassCheckResp* check_resp) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
+        uint8_t null_arr[4] = {};
+        bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
+        bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+
+        if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassCheckResp)) {
+            ret = PicopassErrorProtocol;
+            break;
+        }
+        bit_buffer_write_bytes(instance->rx_buffer, check_resp->data, sizeof(PicopassCheckResp));
+
+    } while(false);
+
+    return ret;
+}
+
+PicopassError picopass_poller_write_block(
+    PicopassPoller* instance,
+    uint8_t block_num,
+    const PicopassBlock* block,
+    const PicopassMac* mac) {
+    PicopassError ret = PicopassErrorNone;
+
+    do {
+        bit_buffer_reset(instance->tx_buffer);
+        bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_UPDATE);
+        bit_buffer_append_byte(instance->tx_buffer, block_num);
+        bit_buffer_append_bytes(instance->tx_buffer, block->data, sizeof(PicopassBlock));
+        bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
+
+        NfcError error = nfc_poller_trx(
+            instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
+        if(error != NfcErrorNone) {
+            ret = picopass_poller_process_error(error);
+            break;
+        }
+
+    } while(false);
+
+    return ret;
+}

+ 85 - 0
protocol/picopass_poller_i.h

@@ -0,0 +1,85 @@
+#pragma once
+
+#include "picopass_poller.h"
+#include "picopass_protocol.h"
+
+#include <nfc/helpers/iso13239_crc.h>
+
+#define PICOPASS_POLLER_BUFFER_SIZE (255)
+#define PICOPASS_CRC_SIZE (2)
+
+typedef enum {
+    PicopassPollerSessionStateIdle,
+    PicopassPollerSessionStateActive,
+    PicopassPollerSessionStateStopRequest,
+} PicopassPollerSessionState;
+
+typedef enum {
+    PicopassPollerStateRequestMode,
+    PicopassPollerStateDetect,
+    PicopassPollerStateSelect,
+    PicopassPollerStatePreAuth,
+    PicopassPollerStateCheckSecurity,
+    PicopassPollerStateAuth,
+    PicopassPollerStateReadBlock,
+    PicopassPollerStateWriteBlock,
+    PicopassPollerStateWriteKey,
+    PicopassPollerStateParseCredential,
+    PicopassPollerStateParseWiegand,
+    PicopassPollerStateSuccess,
+    PicopassPollerStateFail,
+
+    PicopassPollerStateNum,
+} PicopassPollerState;
+
+struct PicopassPoller {
+    Nfc* nfc;
+    PicopassPollerSessionState session_state;
+    PicopassPollerState state;
+    PicopassPollerMode mode;
+
+    PicopassColResSerialNum col_res_serial_num;
+    PicopassSerialNum serial_num;
+    PicopassMac mac;
+    uint8_t div_key[8];
+    uint8_t current_block;
+    uint8_t app_limit;
+
+    PicopassDeviceData* data;
+
+    BitBuffer* tx_buffer;
+    BitBuffer* rx_buffer;
+    BitBuffer* tmp_buffer;
+
+    PicopassPollerEvent event;
+    PicopassPollerEventData event_data;
+    PicopassPollerCallback callback;
+    void* context;
+};
+
+PicopassError picopass_poller_actall(PicopassPoller* instance);
+
+PicopassError
+    picopass_poller_identify(PicopassPoller* instance, PicopassColResSerialNum* col_res_serial_num);
+
+PicopassError picopass_poller_select(
+    PicopassPoller* instance,
+    PicopassColResSerialNum* col_res_serial_num,
+    PicopassSerialNum* serial_num);
+
+PicopassError
+    picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block);
+
+PicopassError
+    picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp);
+
+PicopassError picopass_poller_check(
+    PicopassPoller* instance,
+    PicopassMac* mac,
+    PicopassCheckResp* check_resp);
+
+PicopassError picopass_poller_write_block(
+    PicopassPoller* instance,
+    uint8_t block_num,
+    const PicopassBlock* block,
+    const PicopassMac* mac);

+ 48 - 0
protocol/picopass_protocol.h

@@ -0,0 +1,48 @@
+#pragma once
+
+#include "../picopass_device.h"
+
+#define PICOPASS_BLOCK_LEN 8
+#define PICOPASS_MAX_APP_LIMIT 32
+#define PICOPASS_UID_LEN 8
+#define PICOPASS_READ_CHECK_RESP_LEN 8
+#define PICOPASS_CHECK_RESP_LEN 4
+#define PICOPASS_MAC_LEN 4
+#define PICOPASS_KEY_LEN 8
+
+#define PICOPASS_FDT_LISTEN_FC (1000)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    PicopassErrorNone,
+    PicopassErrorTimeout,
+    PicopassErrorIncorrectCrc,
+    PicopassErrorProtocol,
+} PicopassError;
+
+typedef struct {
+    uint8_t data[RFAL_PICOPASS_UID_LEN];
+} PicopassColResSerialNum;
+
+typedef struct {
+    uint8_t data[RFAL_PICOPASS_UID_LEN];
+} PicopassSerialNum;
+
+typedef struct {
+    uint8_t data[PICOPASS_READ_CHECK_RESP_LEN];
+} PicopassReadCheckResp;
+
+typedef struct {
+    uint8_t data[PICOPASS_CHECK_RESP_LEN];
+} PicopassCheckResp;
+
+typedef struct {
+    uint8_t data[PICOPASS_MAC_LEN];
+} PicopassMac;
+
+#ifdef __cplusplus
+}
+#endif

+ 0 - 213
rfal_picopass.c

@@ -1,213 +0,0 @@
-#include "rfal_picopass.h"
-
-#define RFAL_PICOPASS_TXRX_FLAGS                                                    \
-    (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
-     FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP)
-
-#define TAG "RFAL_PICOPASS"
-
-typedef struct {
-    uint8_t CMD;
-    uint8_t CSN[RFAL_PICOPASS_UID_LEN];
-} rfalPicoPassSelectReq;
-
-typedef struct {
-    uint8_t CMD;
-    uint8_t null[4];
-    uint8_t mac[4];
-} rfalPicoPassCheckReq;
-
-static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) {
-    uint16_t crc = crcSeed;
-    uint8_t dat = dataByte;
-
-    dat ^= (uint8_t)(crc & 0xFFU);
-    dat ^= (dat << 4);
-
-    crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4);
-
-    return crc;
-}
-
-uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) {
-    uint16_t crc = preloadValue;
-    uint16_t index;
-
-    for(index = 0; index < length; index++) {
-        crc = rfalPicoPassUpdateCcitt(crc, buf[index]);
-    }
-
-    return crc;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerInitialize(void) {
-    FuriHalNfcReturn ret;
-
-    ret = furi_hal_nfc_ll_set_mode(
-        FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48);
-    if(ret != FuriHalNfcReturnOk) {
-        return ret;
-    };
-
-    furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
-    furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS);
-    furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER);
-    furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER);
-
-    return FuriHalNfcReturnOk;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) {
-    FuriHalNfcReturn ret;
-    uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL};
-    uint8_t rxBuf[32] = {0};
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt);
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) {
-    FuriHalNfcReturn ret;
-
-    uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY};
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf,
-        sizeof(txBuf),
-        (uint8_t*)idRes,
-        sizeof(rfalPicoPassIdentifyRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN));
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) {
-    FuriHalNfcReturn ret;
-
-    rfalPicoPassSelectReq selReq;
-    selReq.CMD = RFAL_PICOPASS_CMD_SELECT;
-    memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN);
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        (uint8_t*)&selReq,
-        sizeof(rfalPicoPassSelectReq),
-        (uint8_t*)selRes,
-        sizeof(rfalPicoPassSelectRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN));
-    if(ret == FuriHalNfcReturnTimeout) {
-        return FuriHalNfcReturnOk;
-    }
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) {
-    FuriHalNfcReturn ret;
-    uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK_KD, 0x02};
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf,
-        sizeof(txBuf),
-        (uint8_t*)rcRes,
-        sizeof(rfalPicoPassReadCheckRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8));
-
-    if(ret == FuriHalNfcReturnCrc) {
-        return FuriHalNfcReturnOk;
-    }
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) {
-    FuriHalNfcReturn ret;
-    rfalPicoPassCheckReq chkReq;
-    chkReq.CMD = RFAL_PICOPASS_CMD_CHECK;
-    memcpy(chkReq.mac, mac, 4);
-    memset(chkReq.null, 0, 4);
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq)));
-    ret = furi_hal_nfc_ll_txrx(
-        (uint8_t*)&chkReq,
-        sizeof(rfalPicoPassCheckReq),
-        (uint8_t*)chkRes,
-        sizeof(rfalPicoPassCheckRes),
-        &recvLen,
-        flags,
-        fwt);
-    // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4));
-    if(ret == FuriHalNfcReturnCrc) {
-        return FuriHalNfcReturnOk;
-    }
-
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) {
-    FuriHalNfcReturn ret;
-
-    uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ_OR_IDENTIFY, 0, 0, 0};
-    txBuf[1] = blockNum;
-    uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1);
-    memcpy(txBuf + 2, &crc, sizeof(uint16_t));
-
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf,
-        sizeof(txBuf),
-        (uint8_t*)readRes,
-        sizeof(rfalPicoPassReadBlockRes),
-        &recvLen,
-        flags,
-        fwt);
-    return ret;
-}
-
-FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) {
-    FuriHalNfcReturn ret;
-
-    uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_UPDATE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-    memcpy(txBuf + 2, data, PICOPASS_BLOCK_LEN);
-    memcpy(txBuf + 10, mac, 4);
-
-    uint16_t recvLen = 0;
-    uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
-    uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
-    rfalPicoPassReadBlockRes block;
-
-    ret = furi_hal_nfc_ll_txrx(
-        txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt);
-
-    if(ret == FuriHalNfcReturnOk) {
-        // TODO: compare response
-    }
-
-    return ret;
-}

+ 0 - 11
rfal_picopass.h

@@ -62,14 +62,3 @@ typedef struct {
     uint8_t data[PICOPASS_BLOCK_LEN];
     uint8_t crc[2];
 } rfalPicoPassReadBlockRes;
-
-uint16_t rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length);
-
-FuriHalNfcReturn rfalPicoPassPollerInitialize(void);
-FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void);
-FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes);
-FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes);
-FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes);
-FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes);
-FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes);
-FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]);

+ 192 - 126
scenes/picopass_scene_elite_dict_attack.c

@@ -1,172 +1,238 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
+#include "../picopass_keys.h"
 
-#define TAG "IclassEliteDictAttack"
+#define PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE (10)
 
-typedef enum {
-    DictAttackStateIdle,
-    DictAttackStateUserDictInProgress,
-    DictAttackStateFlipperDictInProgress,
-    DictAttackStateStandardDictInProgress,
-} DictAttackState;
+enum {
+    PicopassSceneEliteDictAttackDictEliteUser,
+    PicopassSceneEliteDictAttackDictStandart,
+    PicopassSceneEliteDictAttackDictElite,
+};
 
-void picopass_dict_attack_worker_callback(PicopassWorkerEvent event, void* context) {
-    furi_assert(context);
-    Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+const char* picopass_dict_name[] = {
+    [PicopassSceneEliteDictAttackDictEliteUser] = "Elite User Dictionary",
+    [PicopassSceneEliteDictAttackDictStandart] = "Standard System Dictionary",
+    [PicopassSceneEliteDictAttackDictElite] = "Elite System Dictionary",
+};
+
+static bool picopass_elite_dict_attack_change_dict(Picopass* picopass) {
+    bool success = false;
+
+    do {
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
+        if(scene_state == PicopassSceneEliteDictAttackDictElite) break;
+        if(scene_state == PicopassSceneEliteDictAttackDictEliteUser) {
+            if(!nfc_dict_check_presence(PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME)) break;
+            picopass->dict = nfc_dict_alloc(
+                PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME,
+                NfcDictModeOpenExisting,
+                PICOPASS_KEY_LEN);
+            scene_state = PicopassSceneEliteDictAttackDictStandart;
+        } else if(scene_state == PicopassSceneEliteDictAttackDictStandart) {
+            if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break;
+            picopass->dict = nfc_dict_alloc(
+                PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME,
+                NfcDictModeOpenExisting,
+                PICOPASS_KEY_LEN);
+            scene_state = PicopassSceneEliteDictAttackDictElite;
+        }
+        picopass->dict_attack_ctx.card_detected = true;
+        picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict);
+        picopass->dict_attack_ctx.current_key = 0;
+        picopass->dict_attack_ctx.name = picopass_dict_name[scene_state];
+        scene_manager_set_scene_state(
+            picopass->scene_manager, PicopassSceneEliteDictAttack, scene_state);
+        success = true;
+    } while(false);
+
+    return success;
 }
 
-void picopass_dict_attack_result_callback(void* context) {
+NfcCommand picopass_elite_dict_attack_worker_callback(PicopassPollerEvent event, void* context) {
     furi_assert(context);
+    NfcCommand command = NfcCommandContinue;
+
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(
-        picopass->view_dispatcher, PicopassCustomEventDictAttackSkip);
-}
 
-static void
-    picopass_scene_elite_dict_attack_prepare_view(Picopass* picopass, DictAttackState state) {
-    IclassEliteDictAttackData* dict_attack_data =
-        &picopass->dev->dev_data.iclass_elite_dict_attack_data;
-    PicopassWorkerState worker_state = PicopassWorkerStateReady;
-    IclassEliteDict* dict = NULL;
-
-    // Identify scene state
-    if(state == DictAttackStateIdle) {
-        if(iclass_elite_dict_check_presence(IclassEliteDictTypeUser)) {
-            FURI_LOG_D(TAG, "Starting with user dictionary");
-            state = DictAttackStateUserDictInProgress;
-        } else {
-            FURI_LOG_D(TAG, "Starting with standard dictionary");
-            state = DictAttackStateStandardDictInProgress;
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeRead;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        uint8_t key[PICOPASS_KEY_LEN] = {};
+        bool is_key_provided = true;
+        if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) {
+            if(picopass_elite_dict_attack_change_dict(picopass)) {
+                is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN);
+                view_dispatcher_send_custom_event(
+                    picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
+            } else {
+                is_key_provided = false;
+            }
         }
-    } else if(state == DictAttackStateUserDictInProgress) {
-        FURI_LOG_D(TAG, "Moving from user dictionary to standard dictionary");
-        state = DictAttackStateStandardDictInProgress;
-    } else if(state == DictAttackStateStandardDictInProgress) {
-        FURI_LOG_D(TAG, "Moving from standard dictionary to elite dictionary");
-        state = DictAttackStateFlipperDictInProgress;
-    }
-
-    // Setup view
-    if(state == DictAttackStateUserDictInProgress) {
-        worker_state = PicopassWorkerStateEliteDictAttack;
-        dict_attack_set_header(picopass->dict_attack, "Elite User Dictionary");
-        dict_attack_data->type = IclassEliteDictTypeUser;
-        dict = iclass_elite_dict_alloc(IclassEliteDictTypeUser);
-
-        // If failed to load user dictionary - try the system dictionary
-        if(!dict) {
-            FURI_LOG_E(TAG, "User dictionary not found");
-            state = DictAttackStateStandardDictInProgress;
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
+        memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN);
+        event.data->req_key.is_elite_key =
+            (scene_state != PicopassSceneEliteDictAttackDictStandart);
+        event.data->req_key.is_key_provided = is_key_provided;
+        if(is_key_provided) {
+            picopass->dict_attack_ctx.current_key++;
+            if(picopass->dict_attack_ctx.current_key %
+                   PICOPASS_SCENE_DICT_ATTACK_KEYS_BATCH_UPDATE ==
+               0) {
+                view_dispatcher_send_custom_event(
+                    picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
+            }
         }
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeCardLost) {
+        picopass->dict_attack_ctx.card_detected = false;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
+    } else if(event.type == PicopassPollerEventTypeCardDetected) {
+        picopass->dict_attack_ctx.card_detected = true;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventDictAttackUpdateView);
     }
-    if(state == DictAttackStateStandardDictInProgress) {
-        worker_state = PicopassWorkerStateEliteDictAttack;
-        dict_attack_set_header(picopass->dict_attack, "Standard System Dictionary");
-        dict_attack_data->type = IclassStandardDictTypeFlipper;
-        dict = iclass_elite_dict_alloc(IclassStandardDictTypeFlipper);
-
-        if(!dict) {
-            FURI_LOG_E(TAG, "Flipper standard dictionary not found");
-            state = DictAttackStateFlipperDictInProgress;
-        }
+
+    return command;
+}
+
+static void picopass_scene_elite_dict_attack_update_view(Picopass* instance) {
+    if(instance->dict_attack_ctx.card_detected) {
+        dict_attack_set_card_detected(instance->dict_attack);
+        dict_attack_set_header(instance->dict_attack, instance->dict_attack_ctx.name);
+        dict_attack_set_total_dict_keys(
+            instance->dict_attack, instance->dict_attack_ctx.total_keys);
+        dict_attack_set_current_dict_key(
+            instance->dict_attack, instance->dict_attack_ctx.current_key);
+    } else {
+        dict_attack_set_card_removed(instance->dict_attack);
     }
-    if(state == DictAttackStateFlipperDictInProgress) {
-        worker_state = PicopassWorkerStateEliteDictAttack;
-        dict_attack_set_header(picopass->dict_attack, "Elite System Dictionary");
-        dict_attack_data->type = IclassEliteDictTypeFlipper;
-        dict = iclass_elite_dict_alloc(IclassEliteDictTypeFlipper);
-        if(!dict) {
-            FURI_LOG_E(TAG, "Flipper Elite dictionary not found");
-            // Pass through to let the worker handle the failure
+}
+
+static void picopass_scene_elite_dict_attack_callback(void* context) {
+    Picopass* instance = context;
+
+    view_dispatcher_send_custom_event(
+        instance->view_dispatcher, PicopassCustomEventDictAttackSkip);
+}
+
+void picopass_scene_elite_dict_attack_on_enter(void* context) {
+    Picopass* picopass = context;
+    dolphin_deed(DolphinDeedNfcRead);
+
+    // Setup dict attack context
+    uint32_t state = PicopassSceneEliteDictAttackDictEliteUser;
+
+    bool use_user_dict = nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_USER_NAME);
+    if(use_user_dict) {
+        picopass->dict = nfc_dict_alloc(
+            PICOPASS_ICLASS_ELITE_DICT_USER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+        if(nfc_dict_get_total_keys(picopass->dict) == 0) {
+            nfc_dict_free(picopass->dict);
+            use_user_dict = false;
         }
     }
-    // Free previous dictionary
-    if(dict_attack_data->dict) {
-        iclass_elite_dict_free(dict_attack_data->dict);
+    if(use_user_dict) {
+        state = PicopassSceneEliteDictAttackDictEliteUser;
+    } else {
+        picopass->dict = nfc_dict_alloc(
+            PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+        state = PicopassSceneEliteDictAttackDictStandart;
     }
-    dict_attack_data->dict = dict;
+    picopass->dict_attack_ctx.card_detected = true;
+    picopass->dict_attack_ctx.total_keys = nfc_dict_get_total_keys(picopass->dict);
+    picopass->dict_attack_ctx.current_key = 0;
+    picopass->dict_attack_ctx.name = picopass_dict_name[state];
     scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack, state);
+
+    // Setup view
+    picopass_scene_elite_dict_attack_update_view(picopass);
     dict_attack_set_callback(
-        picopass->dict_attack, picopass_dict_attack_result_callback, picopass);
-    dict_attack_set_current_sector(picopass->dict_attack, 0);
-    dict_attack_set_card_detected(picopass->dict_attack);
-    dict_attack_set_total_dict_keys(
-        picopass->dict_attack, dict ? iclass_elite_dict_get_total_keys(dict) : 0);
-    picopass_worker_start(
-        picopass->worker,
-        worker_state,
-        &picopass->dev->dev_data,
-        picopass_dict_attack_worker_callback,
-        picopass);
-}
+        picopass->dict_attack, picopass_scene_elite_dict_attack_callback, picopass);
+
+    // Start worker
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_elite_dict_attack_worker_callback, picopass);
 
-void picopass_scene_elite_dict_attack_on_enter(void* context) {
-    Picopass* picopass = context;
-    picopass_scene_elite_dict_attack_prepare_view(picopass, DictAttackStateIdle);
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewDictAttack);
     picopass_blink_start(picopass);
-    notification_message(picopass->notifications, &sequence_display_backlight_enforce_on);
 }
 
 bool picopass_scene_elite_dict_attack_on_event(void* context, SceneManagerEvent event) {
     Picopass* picopass = context;
     bool consumed = false;
 
-    uint32_t state =
-        scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneEliteDictAttack);
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassWorkerEventSuccess) {
-            if(state == DictAttackStateUserDictInProgress ||
-               state == DictAttackStateStandardDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                picopass_scene_elite_dict_attack_prepare_view(picopass, state);
-                consumed = true;
+        if(event.event == PicopassCustomEventPollerSuccess) {
+            if(memcmp(
+                   picopass->dev->dev_data.pacs.key,
+                   picopass_factory_debit_key,
+                   PICOPASS_BLOCK_LEN) == 0) {
+                scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess);
             } else {
                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
-                consumed = true;
             }
-        } else if(event.event == PicopassWorkerEventAborted) {
-            scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
-            consumed = true;
-        } else if(event.event == PicopassWorkerEventCardDetected) {
-            dict_attack_set_card_detected(picopass->dict_attack);
             consumed = true;
-        } else if(event.event == PicopassWorkerEventNoCardDetected) {
-            dict_attack_set_card_removed(picopass->dict_attack);
-            consumed = true;
-        } else if(event.event == PicopassWorkerEventNewDictKeyBatch) {
-            dict_attack_inc_current_dict_key(picopass->dict_attack, PICOPASS_DICT_KEY_BATCH_SIZE);
+        } else if(event.event == PicopassCustomEventDictAttackUpdateView) {
+            picopass_scene_elite_dict_attack_update_view(picopass);
             consumed = true;
         } else if(event.event == PicopassCustomEventDictAttackSkip) {
-            if(state == DictAttackStateUserDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                consumed = true;
-            } else if(state == DictAttackStateFlipperDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                consumed = true;
-            } else if(state == DictAttackStateStandardDictInProgress) {
-                picopass_worker_stop(picopass->worker);
-                consumed = true;
+            uint32_t scene_state = scene_manager_get_scene_state(
+                picopass->scene_manager, PicopassSceneEliteDictAttack);
+            if(scene_state != PicopassSceneEliteDictAttackDictElite) {
+                picopass_elite_dict_attack_change_dict(picopass);
+                picopass_scene_elite_dict_attack_update_view(picopass);
+            } else {
+                if(memcmp(
+                       picopass->dev->dev_data.pacs.key,
+                       picopass_factory_debit_key,
+                       PICOPASS_BLOCK_LEN) == 0) {
+                    scene_manager_next_scene(
+                        picopass->scene_manager, PicopassSceneReadFactorySuccess);
+                } else {
+                    scene_manager_next_scene(
+                        picopass->scene_manager, PicopassSceneReadCardSuccess);
+                }
             }
+            consumed = true;
         }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_previous_scene(picopass->scene_manager);
     }
     return consumed;
 }
 
 void picopass_scene_elite_dict_attack_on_exit(void* context) {
     Picopass* picopass = context;
-    IclassEliteDictAttackData* dict_attack_data =
-        &picopass->dev->dev_data.iclass_elite_dict_attack_data;
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
-    if(dict_attack_data->dict) {
-        iclass_elite_dict_free(dict_attack_data->dict);
-        dict_attack_data->dict = NULL;
+
+    if(picopass->dict) {
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
     }
-    dict_attack_reset(picopass->dict_attack);
+    picopass->dict_attack_ctx.current_key = 0;
+    picopass->dict_attack_ctx.total_keys = 0;
+
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
+
+    // Clear view
+    popup_reset(picopass->popup);
+    scene_manager_set_scene_state(
+        picopass->scene_manager,
+        PicopassSceneEliteDictAttack,
+        PicopassSceneEliteDictAttackDictEliteUser);
+
     picopass_blink_stop(picopass);
-    notification_message(picopass->notifications, &sequence_display_backlight_enforce_auto);
 }

+ 10 - 17
scenes/picopass_scene_emulate.c

@@ -1,10 +1,11 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 
-void picopass_emulate_worker_callback(PicopassWorkerEvent event, void* context) {
-    furi_assert(context);
-    Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+NfcCommand picopass_scene_listener_callback(PicopassListenerEvent event, void* context) {
+    UNUSED(event);
+    UNUSED(context);
+
+    return NfcCommandContinue;
 }
 
 void picopass_scene_emulate_on_enter(void* context) {
@@ -17,18 +18,11 @@ void picopass_scene_emulate_on_enter(void* context) {
     widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating");
     widget_add_string_element(widget, 89, 42, AlignCenter, AlignTop, FontPrimary, "PicoPass");
 
-    // Setup view
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
-
-    // Start worker
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateEmulate,
-        &picopass->dev->dev_data,
-        picopass_emulate_worker_callback,
-        picopass);
-
     picopass_blink_emulate_start(picopass);
+
+    picopass->listener = picopass_listener_alloc(picopass->nfc, &picopass->dev->dev_data);
+    picopass_listener_start(picopass->listener, picopass_scene_listener_callback, picopass);
 }
 
 bool picopass_scene_emulate_on_event(void* context, SceneManagerEvent event) {
@@ -49,9 +43,8 @@ void picopass_scene_emulate_on_exit(void* context) {
     Picopass* picopass = context;
 
     picopass_blink_stop(picopass);
-
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
+    picopass_listener_stop(picopass->listener);
+    picopass_listener_free(picopass->listener);
 
     // Clear view
     widget_reset(picopass->widget);

+ 2 - 2
scenes/picopass_scene_key_input.c

@@ -6,8 +6,8 @@
 void picopass_scene_key_input_text_input_callback(void* context) {
     Picopass* picopass = context;
 
-    picopass->dev->dev_data.pacs.elite_kdf = true;
-    memcpy(picopass->dev->dev_data.pacs.key, picopass->byte_input_store, PICOPASS_BLOCK_LEN);
+    memcpy(picopass->write_key_context.key_to_write, picopass->byte_input_store, PICOPASS_KEY_LEN);
+    picopass->write_key_context.is_elite = true;
     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventByteInputDone);
 }
 

+ 9 - 8
scenes/picopass_scene_key_menu.c

@@ -65,29 +65,30 @@ bool picopass_scene_key_menu_on_event(void* context, SceneManagerEvent event) {
         if(event.event == SubmenuIndexWriteStandard) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteStandard);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = false;
+            memcpy(
+                picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteiCE) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCE);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_xice_key, PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = true;
+            memcpy(picopass->write_key_context.key_to_write, picopass_xice_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = true;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteiCL) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCL);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_xicl_key, PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = false;
+            memcpy(picopass->write_key_context.key_to_write, picopass_xicl_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteiCS) {
             scene_manager_set_scene_state(
                 picopass->scene_manager, PicopassSceneKeyMenu, SubmenuIndexWriteiCS);
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_xics_key, PICOPASS_BLOCK_LEN);
-            picopass->dev->dev_data.pacs.elite_kdf = false;
+            memcpy(picopass->write_key_context.key_to_write, picopass_xics_key, PICOPASS_KEY_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         } else if(event.event == SubmenuIndexWriteCustom) {

+ 55 - 40
scenes/picopass_scene_loclass.c

@@ -1,88 +1,103 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 
-void picopass_loclass_worker_callback(PicopassWorkerEvent event, void* context) {
-    furi_assert(context);
+static NfcCommand
+    picopass_scene_loclass_listener_callback(PicopassListenerEvent event, void* context) {
+    NfcCommand command = NfcCommandContinue;
+
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+
+    if(event.type == PicopassListenerEventTypeLoclassGotMac) {
+        picopass->loclass_context.macs_collected++;
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventLoclassGotMac);
+    } else if(event.type == PicopassListenerEventTypeLoclassGotStandardKey) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventLoclassGotStandardKey);
+        command = NfcCommandStop;
+    }
+
+    return command;
 }
 
-void picopass_loclass_result_callback(void* context) {
+static void picopass_loclass_result_callback(void* context) {
     furi_assert(context);
+
     Picopass* picopass = context;
     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
 }
 
 void picopass_scene_loclass_on_enter(void* context) {
     Picopass* picopass = context;
+
     dolphin_deed(DolphinDeedNfcEmulate);
 
     scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 0);
 
     loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass);
-
-    // Start worker
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateLoclass,
-        &picopass->dev->dev_data,
-        picopass_loclass_worker_callback,
-        picopass);
-
-    picopass_blink_emulate_start(picopass);
-
     loclass_set_header(picopass->loclass, "Loclass");
 
+    picopass_blink_emulate_start(picopass);
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass);
+
+    PicopassDeviceData* data = malloc(sizeof(PicopassDeviceData));
+    const uint8_t config_block[PICOPASS_BLOCK_LEN] = {
+        0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
+    memcpy(data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, config_block, sizeof(config_block));
+
+    const uint8_t epurse[PICOPASS_BLOCK_LEN] = {0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+    memcpy(data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, epurse, sizeof(epurse));
+
+    const uint8_t aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+    memcpy(data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, aia, sizeof(aia));
+
+    picopass->listener = picopass_listener_alloc(picopass->nfc, data);
+    free(data);
+    if(picopass_listener_set_mode(picopass->listener, PicopassListenerModeLoclass)) {
+        picopass_listener_start(
+            picopass->listener, picopass_scene_loclass_listener_callback, picopass);
+    } else {
+        loclass_set_num_macs(picopass->loclass, 255);
+        loclass_set_header(picopass->loclass, "Error Opening Log File");
+        picopass_listener_free(picopass->listener);
+        picopass->listener = NULL;
+    }
 }
 
 bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) {
-    Picopass* picopass = context;
     bool consumed = false;
+    Picopass* picopass = context;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassWorkerEventLoclassGotMac) {
-            uint32_t loclass_macs_collected =
-                scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);
-            loclass_macs_collected++;
+        if(event.event == PicopassCustomEventLoclassGotMac) {
             notification_message(picopass->notifications, &sequence_single_vibro);
-            scene_manager_set_scene_state(
-                picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected);
-            loclass_set_num_macs(picopass->loclass, loclass_macs_collected);
-            if(loclass_macs_collected >= LOCLASS_MACS_TO_COLLECT) {
+            loclass_set_num_macs(picopass->loclass, picopass->loclass_context.macs_collected);
+            if(picopass->loclass_context.macs_collected >= LOCLASS_MACS_TO_COLLECT) {
                 notification_message(picopass->notifications, &sequence_double_vibro);
                 scene_manager_previous_scene(picopass->scene_manager);
             }
             consumed = true;
-        } else if(event.event == PicopassWorkerEventLoclassGotStandardKey) {
+        } else if(event.event == PicopassCustomEventLoclassGotStandardKey) {
             loclass_set_header(picopass->loclass, "Loclass (Got Std Key)");
             notification_message(picopass->notifications, &sequence_error);
             consumed = true;
-        } else if(event.event == PicopassWorkerEventLoclassFileError) {
-            scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 255);
-            loclass_set_num_macs(picopass->loclass, 255);
-            loclass_set_header(picopass->loclass, "Error Opening Log File");
-            picopass_blink_stop(picopass);
-            consumed = true;
         } else if(event.event == PicopassCustomEventViewExit) {
             consumed = scene_manager_previous_scene(picopass->scene_manager);
         }
-    } else if(event.type == SceneManagerEventTypeBack) {
-        consumed = scene_manager_previous_scene(picopass->scene_manager);
     }
+
     return consumed;
 }
 
 void picopass_scene_loclass_on_exit(void* context) {
     Picopass* picopass = context;
 
+    if(picopass->listener) {
+        picopass_listener_stop(picopass->listener);
+        picopass_listener_free(picopass->listener);
+    }
+    picopass->loclass_context.macs_collected = 0;
     picopass_blink_stop(picopass);
 
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
-
     loclass_reset(picopass->loclass);
-
-    // Clear view
-    widget_reset(picopass->widget);
-}
+}

+ 78 - 13
scenes/picopass_scene_read_card.c

@@ -2,10 +2,68 @@
 #include <dolphin/dolphin.h>
 #include "../picopass_keys.h"
 
-void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
-    UNUSED(event);
+enum {
+    PicopassSceneReadCardDictStandart,
+    PicopassSceneReadCardDictElite,
+};
+
+static bool picopass_read_card_change_dict(Picopass* picopass) {
+    bool success = false;
+
+    do {
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard);
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
+        if(scene_state == PicopassSceneReadCardDictElite) break;
+        if(!nfc_dict_check_presence(PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME)) break;
+
+        picopass->dict = nfc_dict_alloc(
+            PICOPASS_ICLASS_ELITE_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+        scene_manager_set_scene_state(
+            picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictElite);
+        success = true;
+    } while(false);
+
+    return success;
+}
+
+NfcCommand picopass_read_card_worker_callback(PicopassPollerEvent event, void* context) {
+    furi_assert(context);
+    NfcCommand command = NfcCommandContinue;
+
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
+
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeRead;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        uint8_t key[PICOPASS_KEY_LEN] = {};
+        bool is_key_provided = true;
+        if(!nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN)) {
+            if(picopass_read_card_change_dict(picopass)) {
+                is_key_provided = nfc_dict_get_next_key(picopass->dict, key, PICOPASS_KEY_LEN);
+            } else {
+                is_key_provided = false;
+            }
+        }
+        uint32_t scene_state =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneReadCard);
+        memcpy(event.data->req_key.key, key, PICOPASS_KEY_LEN);
+        event.data->req_key.is_elite_key = (scene_state == PicopassSceneReadCardDictElite);
+        event.data->req_key.is_key_provided = is_key_provided;
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        const PicopassDeviceData* data = picopass_poller_get_data(picopass->poller);
+        memcpy(&picopass->dev->dev_data, data, sizeof(PicopassDeviceData));
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    }
+
+    return command;
 }
 
 void picopass_scene_read_card_on_enter(void* context) {
@@ -17,15 +75,15 @@ void picopass_scene_read_card_on_enter(void* context) {
     popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
 
+    picopass->dict = nfc_dict_alloc(
+        PICOPASS_ICLASS_STANDARD_DICT_FLIPPER_NAME, NfcDictModeOpenExisting, PICOPASS_KEY_LEN);
+    scene_manager_set_scene_state(
+        picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart);
     // Start worker
-    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateDetect,
-        &picopass->dev->dev_data,
-        picopass_read_card_worker_callback,
-        picopass);
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_read_card_worker_callback, picopass);
 
+    view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
     picopass_blink_start(picopass);
 }
 
@@ -34,7 +92,7 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassCustomEventWorkerExit) {
+        if(event.event == PicopassCustomEventPollerSuccess) {
             if(memcmp(
                    picopass->dev->dev_data.pacs.key,
                    picopass_factory_debit_key,
@@ -52,10 +110,17 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
 void picopass_scene_read_card_on_exit(void* context) {
     Picopass* picopass = context;
 
-    // Stop worker
-    picopass_worker_stop(picopass->worker);
+    if(picopass->dict) {
+        nfc_dict_free(picopass->dict);
+        picopass->dict = NULL;
+    }
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
+
     // Clear view
     popup_reset(picopass->popup);
+    scene_manager_set_scene_state(
+        picopass->scene_manager, PicopassSceneReadCard, PicopassSceneReadCardDictStandart);
 
     picopass_blink_stop(picopass);
 }

+ 1 - 0
scenes/picopass_scene_read_card_success.c

@@ -1,5 +1,6 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
+#include <picopass_keys.h>
 
 void picopass_scene_read_card_success_widget_callback(
     GuiButtonType result,

+ 3 - 1
scenes/picopass_scene_read_factory_success.c

@@ -64,7 +64,9 @@ bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEve
         if(event.event == GuiButtonTypeLeft) {
             consumed = scene_manager_previous_scene(picopass->scene_manager);
         } else if(event.event == GuiButtonTypeCenter) {
-            memcpy(picopass->dev->dev_data.pacs.key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
+            memcpy(
+                picopass->write_key_context.key_to_write, picopass_iclass_key, PICOPASS_BLOCK_LEN);
+            picopass->write_key_context.is_elite = false;
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey);
             consumed = true;
         }

+ 44 - 12
scenes/picopass_scene_write_card.c

@@ -1,10 +1,42 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
+#include "../picopass_keys.h"
 
-void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) {
-    UNUSED(event);
+#define PICOPASS_SCENE_WRITE_BLOCK_START 6
+#define PICOPASS_SCENE_WRITE_BLOCK_STOP 10
+
+NfcCommand picopass_scene_write_poller_callback(PicopassPollerEvent event, void* context) {
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, event);
+    NfcCommand command = NfcCommandContinue;
+
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeWrite;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        memcpy(event.data->req_key.key, picopass_iclass_key, sizeof(picopass_iclass_key));
+        event.data->req_key.is_elite_key = false;
+        event.data->req_key.is_key_provided = true;
+    } else if(event.type == PicopassPollerEventTypeRequestWriteBlock) {
+        uint8_t block_num =
+            scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneWriteCard);
+        if(block_num == PICOPASS_SCENE_WRITE_BLOCK_STOP) {
+            event.data->req_write.perform_write = false;
+        } else {
+            event.data->req_write.block_num = block_num;
+            event.data->req_write.block = &picopass->dev->dev_data.AA1[block_num];
+            event.data->req_write.perform_write = true;
+            block_num++;
+            scene_manager_set_scene_state(
+                picopass->scene_manager, PicopassSceneWriteCard, block_num);
+        }
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerFail);
+    }
+
+    return command;
 }
 
 void picopass_scene_write_card_on_enter(void* context) {
@@ -15,15 +47,14 @@ void picopass_scene_write_card_on_enter(void* context) {
     Popup* popup = picopass->popup;
     popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+    scene_manager_set_scene_state(
+        picopass->scene_manager, PicopassSceneWriteCard, PICOPASS_SCENE_WRITE_BLOCK_START);
 
     // Start worker
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateWrite,
-        &picopass->dev->dev_data,
-        picopass_write_card_worker_callback,
-        picopass);
+
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_scene_write_poller_callback, picopass);
 
     picopass_blink_start(picopass);
 }
@@ -33,10 +64,10 @@ bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event)
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassWorkerEventFail) {
+        if(event.event == PicopassCustomEventPollerFail) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure);
             consumed = true;
-        } else if(event.event == PicopassWorkerEventSuccess) {
+        } else if(event.event == PicopassCustomEventPollerSuccess) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
             consumed = true;
         }
@@ -48,7 +79,8 @@ void picopass_scene_write_card_on_exit(void* context) {
     Picopass* picopass = context;
 
     // Stop worker
-    picopass_worker_stop(picopass->worker);
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
     // Clear view
     popup_reset(picopass->popup);
 

+ 35 - 12
scenes/picopass_scene_write_key.c

@@ -1,10 +1,32 @@
 #include "../picopass_i.h"
 #include <dolphin/dolphin.h>
 
-void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) {
-    UNUSED(event);
+NfcCommand picopass_scene_write_key_poller_callback(PicopassPollerEvent event, void* context) {
+    NfcCommand command = NfcCommandContinue;
     Picopass* picopass = context;
-    view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
+
+    if(event.type == PicopassPollerEventTypeRequestMode) {
+        event.data->req_mode.mode = PicopassPollerModeWriteKey;
+    } else if(event.type == PicopassPollerEventTypeRequestKey) {
+        event.data->req_key.is_key_provided = true;
+        memcpy(event.data->req_key.key, picopass->dev->dev_data.pacs.key, PICOPASS_KEY_LEN);
+        event.data->req_key.is_elite_key = picopass->dev->dev_data.pacs.elite_kdf;
+    } else if(event.type == PicopassPollerEventTypeRequestWriteKey) {
+        event.data->req_write_key.data = &picopass->dev->dev_data;
+        memcpy(
+            event.data->req_write_key.key,
+            picopass->write_key_context.key_to_write,
+            PICOPASS_KEY_LEN);
+        event.data->req_write_key.is_elite_key = picopass->write_key_context.is_elite;
+    } else if(event.type == PicopassPollerEventTypeSuccess) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerSuccess);
+    } else if(event.type == PicopassPollerEventTypeFail) {
+        view_dispatcher_send_custom_event(
+            picopass->view_dispatcher, PicopassCustomEventPollerFail);
+    }
+
+    return command;
 }
 
 void picopass_scene_write_key_on_enter(void* context) {
@@ -18,14 +40,10 @@ void picopass_scene_write_key_on_enter(void* context) {
 
     // Start worker
     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
-    picopass_worker_start(
-        picopass->worker,
-        PicopassWorkerStateWriteKey,
-        &picopass->dev->dev_data,
-        picopass_write_key_worker_callback,
-        picopass);
-
     picopass_blink_start(picopass);
+
+    picopass->poller = picopass_poller_alloc(picopass->nfc);
+    picopass_poller_start(picopass->poller, picopass_scene_write_key_poller_callback, picopass);
 }
 
 bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
@@ -33,9 +51,12 @@ bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == PicopassCustomEventWorkerExit) {
+        if(event.event == PicopassCustomEventPollerSuccess) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
             consumed = true;
+        } else if(event.event == PicopassCustomEventPollerFail) {
+            scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardFailure);
+            consumed = true;
         }
     }
     return consumed;
@@ -45,7 +66,9 @@ void picopass_scene_write_key_on_exit(void* context) {
     Picopass* picopass = context;
 
     // Stop worker
-    picopass_worker_stop(picopass->worker);
+    picopass_poller_stop(picopass->poller);
+    picopass_poller_free(picopass->poller);
+
     // Clear view
     popup_reset(picopass->popup);
 

+ 9 - 2
views/dict_attack.c

@@ -15,7 +15,6 @@ struct DictAttack {
 
 typedef struct {
     DictAttackState state;
-    MfClassicType type;
     FuriString* header;
     uint8_t sectors_total;
     uint8_t sectors_read;
@@ -123,7 +122,6 @@ void dict_attack_reset(DictAttack* dict_attack) {
         DictAttackViewModel * model,
         {
             model->state = DictAttackStateRead;
-            model->type = MfClassicType1k;
             model->sectors_total = 1;
             model->sectors_read = 0;
             model->sector_current = 0;
@@ -242,6 +240,15 @@ void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys
         true);
 }
 
+void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view,
+        DictAttackViewModel * model,
+        { model->dict_keys_current = current_key; },
+        true);
+}
+
 void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried) {
     furi_assert(dict_attack);
     with_view_model(

+ 2 - 2
views/dict_attack.h

@@ -3,8 +3,6 @@
 #include <gui/view.h>
 #include <gui/modules/widget.h>
 
-#include <lib/nfc/protocols/mifare_classic.h>
-
 typedef struct DictAttack DictAttack;
 
 typedef void (*DictAttackCallback)(void* context);
@@ -37,6 +35,8 @@ void dict_attack_inc_keys_found(DictAttack* dict_attack);
 
 void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total);
 
+void dict_attack_set_current_dict_key(DictAttack* dict_attack, uint16_t current_key);
+
 void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried);
 
 void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector);

+ 2 - 1
views/loclass.c

@@ -1,5 +1,6 @@
 #include "loclass.h"
-#include "../picopass_worker_i.h"
+
+#include <picopass_device.h>
 
 #include <gui/elements.h>