[FL-2245] Introduce Mifare Classic Emulation (#1242)
* digital signal: introduce digital signal * nfca: add nfca signal encoder * nfc: add mifare classic emulation scene * nfca: add classic emulation support to lib and hal * mifare classic: support basic read commands * nfc: add mifare classic menu scene * mifare classic: start parsing commands in emulation * mifare classic: add nested auth * nfc: fix errors * mifare classic: add encrypt function * nfc: fix mifare classic save * lib hex: add hex uint64_t ASCII parser * flipper format: add uint64 hex format support * nfc: add mifare classic key map * nfc: hide mifare classic keys on emulation * mifare classic: add NACK responce * nfc: add partial bytes support in transparent mode * nfc: mifare classic add shadow file support * digital signal: move arr buffer from BSS to heap * mifare classic: process access bits more careful * nfca: fix memory leack * nfc: format sources * mifare classic: cleun up Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									2017baac48
								
							
						
					
					
						commit
						d31578508a
					
				@ -173,6 +173,8 @@ int32_t nfc_app(void* p) {
 | 
			
		||||
        if(nfc_device_load(nfc->dev, p)) {
 | 
			
		||||
            if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
 | 
			
		||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
 | 
			
		||||
            } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
 | 
			
		||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
 | 
			
		||||
            } else {
 | 
			
		||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,9 @@
 | 
			
		||||
static const char* nfc_file_header = "Flipper NFC device";
 | 
			
		||||
static const uint32_t nfc_file_version = 2;
 | 
			
		||||
 | 
			
		||||
// Protocols format versions
 | 
			
		||||
static const uint32_t nfc_mifare_classic_data_format_version = 1;
 | 
			
		||||
 | 
			
		||||
NfcDevice* nfc_device_alloc() {
 | 
			
		||||
    NfcDevice* nfc_dev = malloc(sizeof(NfcDevice));
 | 
			
		||||
    nfc_dev->storage = furi_record_open("storage");
 | 
			
		||||
@ -624,6 +627,7 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
 | 
			
		||||
    // Save Mifare Classic specific data
 | 
			
		||||
    do {
 | 
			
		||||
        if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break;
 | 
			
		||||
 | 
			
		||||
        if(data->type == MfClassicType1k) {
 | 
			
		||||
            if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
 | 
			
		||||
            blocks = 64;
 | 
			
		||||
@ -631,8 +635,17 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
 | 
			
		||||
            if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break;
 | 
			
		||||
            blocks = 256;
 | 
			
		||||
        }
 | 
			
		||||
        if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break;
 | 
			
		||||
        if(!flipper_format_write_uint32(
 | 
			
		||||
               file, "Data format version", &nfc_mifare_classic_data_format_version, 1))
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        if(!flipper_format_write_comment_cstr(
 | 
			
		||||
               file, "Key map is the bit mask indicating valid key in each sector"))
 | 
			
		||||
            break;
 | 
			
		||||
        if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
 | 
			
		||||
        if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
 | 
			
		||||
 | 
			
		||||
        if(!flipper_format_write_comment_cstr(file, "Mifare Classic blocks")) break;
 | 
			
		||||
        bool block_saved = true;
 | 
			
		||||
        for(size_t i = 0; i < blocks; i++) {
 | 
			
		||||
            string_printf(temp_str, "Block %d", i);
 | 
			
		||||
@ -654,6 +667,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
 | 
			
		||||
    bool parsed = false;
 | 
			
		||||
    MfClassicData* data = &dev->dev_data.mf_classic_data;
 | 
			
		||||
    string_t temp_str;
 | 
			
		||||
    uint32_t data_format_version = 0;
 | 
			
		||||
    string_init(temp_str);
 | 
			
		||||
    uint16_t data_blocks = 0;
 | 
			
		||||
 | 
			
		||||
@ -669,6 +683,19 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
 | 
			
		||||
        } else {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Read Mifare Classic format version
 | 
			
		||||
        if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) {
 | 
			
		||||
            // Load unread sectors with zero keys access for backward compatability
 | 
			
		||||
            if(!flipper_format_rewind(file)) break;
 | 
			
		||||
            data->key_a_mask = 0xffffffffffffffff;
 | 
			
		||||
            data->key_b_mask = 0xffffffffffffffff;
 | 
			
		||||
        } else {
 | 
			
		||||
            if(data_format_version != nfc_mifare_classic_data_format_version) break;
 | 
			
		||||
            if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
 | 
			
		||||
            if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Read Mifare Classic blocks
 | 
			
		||||
        bool block_read = true;
 | 
			
		||||
        for(size_t i = 0; i < data_blocks; i++) {
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
#include <lib/nfc_protocols/mifare_ultralight.h>
 | 
			
		||||
#include <lib/nfc_protocols/mifare_classic.h>
 | 
			
		||||
#include <lib/nfc_protocols/mifare_desfire.h>
 | 
			
		||||
#include <lib/nfc_protocols/nfca.h>
 | 
			
		||||
 | 
			
		||||
#include "helpers/nfc_mf_classic_dict.h"
 | 
			
		||||
 | 
			
		||||
@ -104,6 +105,8 @@ int32_t nfc_worker_task(void* context) {
 | 
			
		||||
        nfc_worker_emulate_mifare_ul(nfc_worker);
 | 
			
		||||
    } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
 | 
			
		||||
        nfc_worker_mifare_classic_dict_attack(nfc_worker);
 | 
			
		||||
    } else if(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
 | 
			
		||||
        nfc_worker_emulate_mifare_classic(nfc_worker);
 | 
			
		||||
    } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) {
 | 
			
		||||
        nfc_worker_read_mifare_desfire(nfc_worker);
 | 
			
		||||
    }
 | 
			
		||||
@ -474,6 +477,34 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) {
 | 
			
		||||
    stream_free(nfc_worker->dict_stream);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
 | 
			
		||||
    FuriHalNfcTxRxContext tx_rx;
 | 
			
		||||
    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
 | 
			
		||||
    MfClassicEmulator emulator = {
 | 
			
		||||
        .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
 | 
			
		||||
        .data = nfc_worker->dev_data->mf_classic_data,
 | 
			
		||||
        .data_changed = false,
 | 
			
		||||
    };
 | 
			
		||||
    NfcaSignal* nfca_signal = nfca_signal_alloc();
 | 
			
		||||
    tx_rx.nfca_signal = nfca_signal;
 | 
			
		||||
 | 
			
		||||
    while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
 | 
			
		||||
        if(furi_hal_nfc_listen(
 | 
			
		||||
               nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) {
 | 
			
		||||
            mf_classic_emulator(&emulator, &tx_rx);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if(emulator.data_changed) {
 | 
			
		||||
        nfc_worker->dev_data->mf_classic_data = emulator.data;
 | 
			
		||||
        if(nfc_worker->callback) {
 | 
			
		||||
            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
 | 
			
		||||
        }
 | 
			
		||||
        emulator.data_changed = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    nfca_signal_free(nfca_signal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
 | 
			
		||||
    ReturnCode err;
 | 
			
		||||
    uint8_t tx_buff[64] = {};
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ typedef enum {
 | 
			
		||||
    NfcWorkerStateReadMifareUltralight,
 | 
			
		||||
    NfcWorkerStateEmulateMifareUltralight,
 | 
			
		||||
    NfcWorkerStateReadMifareClassic,
 | 
			
		||||
    NfcWorkerStateEmulateMifareClassic,
 | 
			
		||||
    NfcWorkerStateReadMifareDesfire,
 | 
			
		||||
    // Transition
 | 
			
		||||
    NfcWorkerStateStop,
 | 
			
		||||
 | 
			
		||||
@ -34,4 +34,6 @@ ADD_SCENE(nfc, restore_original, RestoreOriginal)
 | 
			
		||||
ADD_SCENE(nfc, debug, Debug)
 | 
			
		||||
ADD_SCENE(nfc, field, Field)
 | 
			
		||||
ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic)
 | 
			
		||||
ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic)
 | 
			
		||||
ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
 | 
			
		||||
ADD_SCENE(nfc, dict_not_found, DictNotFound)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
#include "../nfc_i.h"
 | 
			
		||||
#include <dolphin/dolphin.h>
 | 
			
		||||
 | 
			
		||||
#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL)
 | 
			
		||||
#define NFC_MF_CLASSIC_DATA_CHANGED (1UL)
 | 
			
		||||
 | 
			
		||||
void nfc_emulate_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) {
 | 
			
		||||
    UNUSED(event);
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
 | 
			
		||||
    scene_manager_set_scene_state(
 | 
			
		||||
        nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_CHANGED);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfc_scene_emulate_mifare_classic_on_enter(void* context) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
    DOLPHIN_DEED(DolphinDeedNfcEmulate);
 | 
			
		||||
 | 
			
		||||
    // Setup view
 | 
			
		||||
    Popup* popup = nfc->popup;
 | 
			
		||||
    if(strcmp(nfc->dev->dev_name, "")) {
 | 
			
		||||
        nfc_text_store_set(nfc, "%s", nfc->dev->dev_name);
 | 
			
		||||
    }
 | 
			
		||||
    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
 | 
			
		||||
    popup_set_header(popup, "Emulating\nMf Classic", 56, 31, AlignLeft, AlignTop);
 | 
			
		||||
 | 
			
		||||
    // Setup and start worker
 | 
			
		||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
 | 
			
		||||
    nfc_worker_start(
 | 
			
		||||
        nfc->worker,
 | 
			
		||||
        NfcWorkerStateEmulateMifareClassic,
 | 
			
		||||
        &nfc->dev->dev_data,
 | 
			
		||||
        nfc_emulate_mifare_classic_worker_callback,
 | 
			
		||||
        nfc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
    bool consumed = false;
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeTick) {
 | 
			
		||||
        notification_message(nfc->notifications, &sequence_blink_blue_10);
 | 
			
		||||
        consumed = true;
 | 
			
		||||
    } else if(event.type == SceneManagerEventTypeBack) {
 | 
			
		||||
        // Stop worker
 | 
			
		||||
        nfc_worker_stop(nfc->worker);
 | 
			
		||||
        // Check if data changed and save in shadow file
 | 
			
		||||
        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareClassic) ==
 | 
			
		||||
           NFC_MF_CLASSIC_DATA_CHANGED) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_NOT_CHANGED);
 | 
			
		||||
            nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
 | 
			
		||||
        }
 | 
			
		||||
        consumed = false;
 | 
			
		||||
    }
 | 
			
		||||
    return consumed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfc_scene_emulate_mifare_classic_on_exit(void* context) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
 | 
			
		||||
    // Clear view
 | 
			
		||||
    popup_reset(nfc->popup);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								applications/nfc/scenes/nfc_scene_mifare_classic_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								applications/nfc/scenes/nfc_scene_mifare_classic_menu.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
#include "../nfc_i.h"
 | 
			
		||||
 | 
			
		||||
enum SubmenuIndex {
 | 
			
		||||
    SubmenuIndexSave,
 | 
			
		||||
    SubmenuIndexEmulate,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void nfc_scene_mifare_classic_menu_submenu_callback(void* context, uint32_t index) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfc_scene_mifare_classic_menu_on_enter(void* context) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
    Submenu* submenu = nfc->submenu;
 | 
			
		||||
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_classic_menu_submenu_callback, nfc);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu,
 | 
			
		||||
        "Emulate",
 | 
			
		||||
        SubmenuIndexEmulate,
 | 
			
		||||
        nfc_scene_mifare_classic_menu_submenu_callback,
 | 
			
		||||
        nfc);
 | 
			
		||||
    submenu_set_selected_item(
 | 
			
		||||
        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu));
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
    bool consumed = false;
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubmenuIndexSave) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave);
 | 
			
		||||
            nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
 | 
			
		||||
            // Clear device name
 | 
			
		||||
            nfc_device_set_name(nfc->dev, "");
 | 
			
		||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexEmulate) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate);
 | 
			
		||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        }
 | 
			
		||||
    } else if(event.type == SceneManagerEventTypeBack) {
 | 
			
		||||
        consumed =
 | 
			
		||||
            scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return consumed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfc_scene_mifare_classic_menu_on_exit(void* context) {
 | 
			
		||||
    Nfc* nfc = context;
 | 
			
		||||
 | 
			
		||||
    // Clear view
 | 
			
		||||
    submenu_reset(nfc->submenu);
 | 
			
		||||
}
 | 
			
		||||
@ -47,7 +47,7 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve
 | 
			
		||||
        consumed = true;
 | 
			
		||||
    } else if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == NfcCustomEventDictAttackDone) {
 | 
			
		||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
 | 
			
		||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareClassicMenu);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == NfcWorkerEventDetectedClassic1k) {
 | 
			
		||||
            dict_attack_card_detected(nfc->dict_attack, MfClassicType1k);
 | 
			
		||||
@ -71,7 +71,6 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
 | 
			
		||||
            notification_message(nfc->notifications, &sequence_success);
 | 
			
		||||
            nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
 | 
			
		||||
            dict_attack_set_result(nfc->dict_attack, true);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == NfcWorkerEventFail) {
 | 
			
		||||
 | 
			
		||||
@ -27,13 +27,11 @@ void nfc_scene_saved_menu_on_enter(void* context) {
 | 
			
		||||
            SubmenuIndexEmulate,
 | 
			
		||||
            nfc_scene_saved_menu_submenu_callback,
 | 
			
		||||
            nfc);
 | 
			
		||||
    } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
 | 
			
		||||
    } else if(
 | 
			
		||||
        nfc->dev->format == NfcDeviceSaveFormatMifareUl ||
 | 
			
		||||
        nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
 | 
			
		||||
        submenu_add_item(
 | 
			
		||||
            submenu,
 | 
			
		||||
            "Emulate Ultralight",
 | 
			
		||||
            SubmenuIndexEmulate,
 | 
			
		||||
            nfc_scene_saved_menu_submenu_callback,
 | 
			
		||||
            nfc);
 | 
			
		||||
            submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc);
 | 
			
		||||
    }
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu, "Edit UID and Name", SubmenuIndexEdit, nfc_scene_saved_menu_submenu_callback, nfc);
 | 
			
		||||
@ -64,6 +62,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
        if(event.event == SubmenuIndexEmulate) {
 | 
			
		||||
            if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
 | 
			
		||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
 | 
			
		||||
            } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
 | 
			
		||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
 | 
			
		||||
            } else {
 | 
			
		||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
 | 
			
		||||
            canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, draw_str);
 | 
			
		||||
        } else if(m->state == DictAttackStateSuccess) {
 | 
			
		||||
            canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Complete!");
 | 
			
		||||
            elements_button_right(canvas, "Save");
 | 
			
		||||
            elements_button_right(canvas, "More");
 | 
			
		||||
        } else if(m->state == DictAttackStateFail) {
 | 
			
		||||
            canvas_draw_str_aligned(
 | 
			
		||||
                canvas, 64, 2, AlignCenter, AlignTop, "Failed to read any sector");
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,7 @@ extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FURI_BIT
 | 
			
		||||
#define FURI_BIT(x, n) ((x) >> (n)&1)
 | 
			
		||||
#define FURI_BIT(x, n) (((x) >> (n)) & 1)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FURI_IS_IRQ_MASKED
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
#include "furi_hal_nfc.h"
 | 
			
		||||
#include <st25r3916.h>
 | 
			
		||||
#include <st25r3916_irq.h>
 | 
			
		||||
#include <rfal_rf.h>
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <m-string.h>
 | 
			
		||||
#include <lib/nfc_protocols/nfca.h>
 | 
			
		||||
 | 
			
		||||
#include <lib/digital_signal/digital_signal.h>
 | 
			
		||||
#include <furi_hal_delay.h>
 | 
			
		||||
 | 
			
		||||
#define TAG "FuriHalNfc"
 | 
			
		||||
 | 
			
		||||
@ -394,6 +397,80 @@ ReturnCode furi_hal_nfc_data_exchange(
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
 | 
			
		||||
    furi_assert(tx_rx->nfca_signal);
 | 
			
		||||
 | 
			
		||||
    platformDisableIrqCallback();
 | 
			
		||||
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
 | 
			
		||||
    // Start transparent mode
 | 
			
		||||
    st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);
 | 
			
		||||
    // Reconfigure gpio
 | 
			
		||||
    furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
 | 
			
		||||
    furi_hal_gpio_init(&gpio_spi_r_sck, GpioModeInput, GpioPullUp, GpioSpeedLow);
 | 
			
		||||
    furi_hal_gpio_init(&gpio_spi_r_miso, GpioModeInput, GpioPullUp, GpioSpeedLow);
 | 
			
		||||
    furi_hal_gpio_init(&gpio_nfc_cs, GpioModeInput, GpioPullUp, GpioSpeedLow);
 | 
			
		||||
    furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
 | 
			
		||||
    furi_hal_gpio_write(&gpio_spi_r_mosi, false);
 | 
			
		||||
 | 
			
		||||
    // Send signal
 | 
			
		||||
    nfca_signal_encode(tx_rx->nfca_signal, tx_rx->tx_data, tx_rx->tx_bits, tx_rx->tx_parity);
 | 
			
		||||
    digital_signal_send(tx_rx->nfca_signal->tx_signal, &gpio_spi_r_mosi);
 | 
			
		||||
    furi_hal_gpio_write(&gpio_spi_r_mosi, false);
 | 
			
		||||
 | 
			
		||||
    // Configure gpio back to SPI and exit transparent
 | 
			
		||||
    furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
 | 
			
		||||
    st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA);
 | 
			
		||||
 | 
			
		||||
    // Manually wait for interrupt
 | 
			
		||||
    furi_hal_gpio_init(&gpio_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
 | 
			
		||||
    st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE);
 | 
			
		||||
 | 
			
		||||
    uint32_t irq = 0;
 | 
			
		||||
    uint8_t rxe = 0;
 | 
			
		||||
    uint32_t start = DWT->CYCCNT;
 | 
			
		||||
    while(true) {
 | 
			
		||||
        if(furi_hal_gpio_read(&gpio_rfid_pull) == true) {
 | 
			
		||||
            st25r3916ReadRegister(ST25R3916_REG_IRQ_MAIN, &rxe);
 | 
			
		||||
            if(rxe & (1 << 4)) {
 | 
			
		||||
                irq = 1;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t timeout = DWT->CYCCNT - start;
 | 
			
		||||
        if(timeout / furi_hal_delay_instructions_per_microsecond() > timeout_ms * 1000) {
 | 
			
		||||
            FURI_LOG_D(TAG, "Interrupt waiting timeout");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if(irq) {
 | 
			
		||||
        uint8_t fifo_stat[2];
 | 
			
		||||
        st25r3916ReadMultipleRegisters(
 | 
			
		||||
            ST25R3916_REG_FIFO_STATUS1, fifo_stat, ST25R3916_FIFO_STATUS_LEN);
 | 
			
		||||
        uint16_t len =
 | 
			
		||||
            ((((uint16_t)fifo_stat[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >>
 | 
			
		||||
              ST25R3916_REG_FIFO_STATUS2_fifo_b_shift)
 | 
			
		||||
             << RFAL_BITS_IN_BYTE);
 | 
			
		||||
        len |= (((uint16_t)fifo_stat[0]) & 0x00FFU);
 | 
			
		||||
        uint8_t rx[100];
 | 
			
		||||
        st25r3916ReadFifo(rx, len);
 | 
			
		||||
 | 
			
		||||
        tx_rx->rx_bits = len * 8;
 | 
			
		||||
        memcpy(tx_rx->rx_data, rx, len);
 | 
			
		||||
 | 
			
		||||
        ret = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        FURI_LOG_E(TAG, "Timeout error");
 | 
			
		||||
        ret = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    st25r3916ClearInterrupts();
 | 
			
		||||
    platformEnableIrqCallback();
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) {
 | 
			
		||||
    uint32_t flags = 0;
 | 
			
		||||
 | 
			
		||||
@ -405,6 +482,9 @@ static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) {
 | 
			
		||||
    } else if(type == FuriHalNfcTxRxTypeRaw) {
 | 
			
		||||
        flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP |
 | 
			
		||||
                RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE;
 | 
			
		||||
    } else if(type == FuriHalNfcTxRxTypeRxRaw) {
 | 
			
		||||
        flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP |
 | 
			
		||||
                RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return flags;
 | 
			
		||||
@ -470,6 +550,10 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
 | 
			
		||||
    uint8_t* temp_rx_buff = NULL;
 | 
			
		||||
    uint16_t* temp_rx_bits = NULL;
 | 
			
		||||
 | 
			
		||||
    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTransparent) {
 | 
			
		||||
        return furi_hal_nfc_transparent_tx_rx(tx_rx, timeout_ms);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prepare data for FIFO if necessary
 | 
			
		||||
    uint32_t flags = furi_hal_nfc_tx_rx_get_flag(tx_rx->tx_rx_type);
 | 
			
		||||
    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) {
 | 
			
		||||
@ -502,7 +586,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
 | 
			
		||||
        osDelay(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) {
 | 
			
		||||
    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw ||
 | 
			
		||||
       tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) {
 | 
			
		||||
        tx_rx->rx_bits = 8 * furi_hal_nfc_bitstream_to_data_and_parity(
 | 
			
		||||
                                 temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity);
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,8 @@
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <lib/nfc_protocols/nfca.h>
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
@ -39,6 +41,8 @@ typedef enum {
 | 
			
		||||
    FuriHalNfcTxRxTypeRxNoCrc,
 | 
			
		||||
    FuriHalNfcTxRxTypeRxKeepPar,
 | 
			
		||||
    FuriHalNfcTxRxTypeRaw,
 | 
			
		||||
    FuriHalNfcTxRxTypeRxRaw,
 | 
			
		||||
    FuriHalNfcTxRxTransparent,
 | 
			
		||||
} FuriHalNfcTxRxType;
 | 
			
		||||
 | 
			
		||||
typedef bool (*FuriHalNfcEmulateCallback)(
 | 
			
		||||
@ -80,6 +84,7 @@ typedef struct {
 | 
			
		||||
    uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE];
 | 
			
		||||
    uint16_t rx_bits;
 | 
			
		||||
    FuriHalNfcTxRxType tx_rx_type;
 | 
			
		||||
    NfcaSignal* nfca_signal;
 | 
			
		||||
} FuriHalNfcTxRxContext;
 | 
			
		||||
 | 
			
		||||
/** Init nfc
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										173
									
								
								lib/digital_signal/digital_signal.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								lib/digital_signal/digital_signal.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
			
		||||
#include "digital_signal.h"
 | 
			
		||||
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <stm32wbxx_ll_dma.h>
 | 
			
		||||
#include <stm32wbxx_ll_tim.h>
 | 
			
		||||
#include <math.h>
 | 
			
		||||
 | 
			
		||||
#define F_TIM (64000000.0)
 | 
			
		||||
#define T_TIM (1.0 / F_TIM)
 | 
			
		||||
 | 
			
		||||
DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) {
 | 
			
		||||
    DigitalSignal* signal = malloc(sizeof(DigitalSignal));
 | 
			
		||||
    signal->start_level = true;
 | 
			
		||||
    signal->edges_max_cnt = max_edges_cnt;
 | 
			
		||||
    signal->edge_timings = malloc(max_edges_cnt * sizeof(float));
 | 
			
		||||
    signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t));
 | 
			
		||||
    signal->edge_cnt = 0;
 | 
			
		||||
 | 
			
		||||
    return signal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void digital_signal_free(DigitalSignal* signal) {
 | 
			
		||||
    furi_assert(signal);
 | 
			
		||||
 | 
			
		||||
    free(signal->edge_timings);
 | 
			
		||||
    free(signal->reload_reg_buff);
 | 
			
		||||
    free(signal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) {
 | 
			
		||||
    furi_assert(signal_a);
 | 
			
		||||
    furi_assert(signal_b);
 | 
			
		||||
 | 
			
		||||
    if(signal_a->edges_max_cnt < signal_a->edge_cnt + signal_b->edge_cnt) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool end_level = signal_a->start_level;
 | 
			
		||||
    if(signal_a->edge_cnt) {
 | 
			
		||||
        end_level = signal_a->start_level ^ !(signal_a->edge_cnt % 2);
 | 
			
		||||
    }
 | 
			
		||||
    uint8_t start_copy = 0;
 | 
			
		||||
    if(end_level == signal_b->start_level) {
 | 
			
		||||
        if(signal_a->edge_cnt) {
 | 
			
		||||
            signal_a->edge_timings[signal_a->edge_cnt - 1] += signal_b->edge_timings[0];
 | 
			
		||||
            start_copy += 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    memcpy(
 | 
			
		||||
        &signal_a->edge_timings[signal_a->edge_cnt],
 | 
			
		||||
        &signal_b->edge_timings[start_copy],
 | 
			
		||||
        (signal_b->edge_cnt - start_copy) * sizeof(float));
 | 
			
		||||
    signal_a->edge_cnt += signal_b->edge_cnt - start_copy;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool digital_signal_get_start_level(DigitalSignal* signal) {
 | 
			
		||||
    furi_assert(signal);
 | 
			
		||||
 | 
			
		||||
    return signal->start_level;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) {
 | 
			
		||||
    furi_assert(signal);
 | 
			
		||||
 | 
			
		||||
    return signal->edge_cnt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) {
 | 
			
		||||
    furi_assert(signal);
 | 
			
		||||
    furi_assert(edge_num < signal->edge_cnt);
 | 
			
		||||
 | 
			
		||||
    return signal->edge_timings[edge_num];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void digital_signal_prepare_arr(DigitalSignal* signal) {
 | 
			
		||||
    float t_signal = 0;
 | 
			
		||||
    float t_current = 0;
 | 
			
		||||
    float r = 0;
 | 
			
		||||
    float r_int = 0;
 | 
			
		||||
    float r_dec = 0;
 | 
			
		||||
 | 
			
		||||
    for(size_t i = 0; i < signal->edge_cnt - 1; i++) {
 | 
			
		||||
        t_signal += signal->edge_timings[i];
 | 
			
		||||
        r = (t_signal - t_current) / T_TIM;
 | 
			
		||||
        r_dec = modff(r, &r_int);
 | 
			
		||||
        if(r_dec < 0.5f) {
 | 
			
		||||
            signal->reload_reg_buff[i] = (uint32_t)r_int - 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            signal->reload_reg_buff[i] = (uint32_t)r_int;
 | 
			
		||||
        }
 | 
			
		||||
        t_current += (signal->reload_reg_buff[i] + 1) * T_TIM;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) {
 | 
			
		||||
    furi_assert(signal);
 | 
			
		||||
    furi_assert(gpio);
 | 
			
		||||
 | 
			
		||||
    // Configure gpio as output
 | 
			
		||||
    furi_hal_gpio_init(gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
 | 
			
		||||
 | 
			
		||||
    // Init gpio buffer and DMA channel
 | 
			
		||||
    uint16_t gpio_reg = gpio->port->ODR;
 | 
			
		||||
    uint16_t gpio_buff[2];
 | 
			
		||||
    if(signal->start_level) {
 | 
			
		||||
        gpio_buff[0] = gpio_reg | gpio->pin;
 | 
			
		||||
        gpio_buff[1] = gpio_reg & ~(gpio->pin);
 | 
			
		||||
    } else {
 | 
			
		||||
        gpio_buff[0] = gpio_reg & ~(gpio->pin);
 | 
			
		||||
        gpio_buff[1] = gpio_reg | gpio->pin;
 | 
			
		||||
    }
 | 
			
		||||
    LL_DMA_InitTypeDef dma_config = {};
 | 
			
		||||
    dma_config.MemoryOrM2MDstAddress = (uint32_t)gpio_buff;
 | 
			
		||||
    dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->ODR);
 | 
			
		||||
    dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
 | 
			
		||||
    dma_config.Mode = LL_DMA_MODE_CIRCULAR;
 | 
			
		||||
    dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
 | 
			
		||||
    dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
 | 
			
		||||
    dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
 | 
			
		||||
    dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
 | 
			
		||||
    dma_config.NbData = 2;
 | 
			
		||||
    dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
 | 
			
		||||
    dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH;
 | 
			
		||||
    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config);
 | 
			
		||||
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 2);
 | 
			
		||||
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
 | 
			
		||||
 | 
			
		||||
    // Init timer arr register buffer and DMA channel
 | 
			
		||||
    digital_signal_prepare_arr(signal);
 | 
			
		||||
    dma_config.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff;
 | 
			
		||||
    dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR);
 | 
			
		||||
    dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
 | 
			
		||||
    dma_config.Mode = LL_DMA_MODE_NORMAL;
 | 
			
		||||
    dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
 | 
			
		||||
    dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
 | 
			
		||||
    dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
 | 
			
		||||
    dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
 | 
			
		||||
    dma_config.NbData = signal->edge_cnt - 2;
 | 
			
		||||
    dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
 | 
			
		||||
    dma_config.Priority = LL_DMA_PRIORITY_HIGH;
 | 
			
		||||
    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config);
 | 
			
		||||
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, signal->edge_cnt - 2);
 | 
			
		||||
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
 | 
			
		||||
 | 
			
		||||
    // Set up timer
 | 
			
		||||
    LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
 | 
			
		||||
    LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
 | 
			
		||||
    LL_TIM_SetPrescaler(TIM2, 0);
 | 
			
		||||
    LL_TIM_SetAutoReload(TIM2, 10);
 | 
			
		||||
    LL_TIM_SetCounter(TIM2, 0);
 | 
			
		||||
    LL_TIM_EnableUpdateEvent(TIM2);
 | 
			
		||||
    LL_TIM_EnableDMAReq_UPDATE(TIM2);
 | 
			
		||||
 | 
			
		||||
    // Start transactions
 | 
			
		||||
    LL_TIM_GenerateEvent_UPDATE(TIM2); // Do we really need it?
 | 
			
		||||
    LL_TIM_EnableCounter(TIM2);
 | 
			
		||||
 | 
			
		||||
    while(!LL_DMA_IsActiveFlag_TC2(DMA1))
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
    LL_DMA_ClearFlag_TC1(DMA1);
 | 
			
		||||
    LL_DMA_ClearFlag_TC2(DMA1);
 | 
			
		||||
    LL_TIM_DisableCounter(TIM2);
 | 
			
		||||
    LL_TIM_SetCounter(TIM2, 0);
 | 
			
		||||
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
 | 
			
		||||
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								lib/digital_signal/digital_signal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/digital_signal/digital_signal.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <furi_hal_gpio.h>
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    bool start_level;
 | 
			
		||||
    uint32_t edge_cnt;
 | 
			
		||||
    uint32_t edges_max_cnt;
 | 
			
		||||
    float* edge_timings;
 | 
			
		||||
    uint32_t* reload_reg_buff;
 | 
			
		||||
} DigitalSignal;
 | 
			
		||||
 | 
			
		||||
DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt);
 | 
			
		||||
 | 
			
		||||
void digital_signal_free(DigitalSignal* signal);
 | 
			
		||||
 | 
			
		||||
bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b);
 | 
			
		||||
 | 
			
		||||
bool digital_signal_get_start_level(DigitalSignal* signal);
 | 
			
		||||
 | 
			
		||||
uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal);
 | 
			
		||||
 | 
			
		||||
float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num);
 | 
			
		||||
 | 
			
		||||
bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio);
 | 
			
		||||
@ -185,6 +185,37 @@ bool flipper_format_write_string_cstr(
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool flipper_format_read_hex_uint64(
 | 
			
		||||
    FlipperFormat* flipper_format,
 | 
			
		||||
    const char* key,
 | 
			
		||||
    uint64_t* data,
 | 
			
		||||
    const uint16_t data_size) {
 | 
			
		||||
    furi_assert(flipper_format);
 | 
			
		||||
    return flipper_format_stream_read_value_line(
 | 
			
		||||
        flipper_format->stream,
 | 
			
		||||
        key,
 | 
			
		||||
        FlipperStreamValueHexUint64,
 | 
			
		||||
        data,
 | 
			
		||||
        data_size,
 | 
			
		||||
        flipper_format->strict_mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool flipper_format_write_hex_uint64(
 | 
			
		||||
    FlipperFormat* flipper_format,
 | 
			
		||||
    const char* key,
 | 
			
		||||
    const uint64_t* data,
 | 
			
		||||
    const uint16_t data_size) {
 | 
			
		||||
    furi_assert(flipper_format);
 | 
			
		||||
    FlipperStreamWriteData write_data = {
 | 
			
		||||
        .key = key,
 | 
			
		||||
        .type = FlipperStreamValueHexUint64,
 | 
			
		||||
        .data = data,
 | 
			
		||||
        .data_size = data_size,
 | 
			
		||||
    };
 | 
			
		||||
    bool result = flipper_format_stream_write_value_line(flipper_format->stream, &write_data);
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool flipper_format_read_uint32(
 | 
			
		||||
    FlipperFormat* flipper_format,
 | 
			
		||||
    const char* key,
 | 
			
		||||
 | 
			
		||||
@ -273,6 +273,34 @@ bool flipper_format_write_string_cstr(
 | 
			
		||||
    const char* key,
 | 
			
		||||
    const char* data);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Read array of uint64 in hex format by key
 | 
			
		||||
 * @param flipper_format Pointer to a FlipperFormat instance
 | 
			
		||||
 * @param key Key
 | 
			
		||||
 * @param data Value
 | 
			
		||||
 * @param data_size Values count
 | 
			
		||||
 * @return True on success
 | 
			
		||||
 */
 | 
			
		||||
bool flipper_format_read_hex_uint64(
 | 
			
		||||
    FlipperFormat* flipper_format,
 | 
			
		||||
    const char* key,
 | 
			
		||||
    uint64_t* data,
 | 
			
		||||
    const uint16_t data_size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Write key and array of uint64 in hex format
 | 
			
		||||
 * @param flipper_format Pointer to a FlipperFormat instance
 | 
			
		||||
 * @param key Key
 | 
			
		||||
 * @param data Value
 | 
			
		||||
 * @param data_size Values count
 | 
			
		||||
 * @return True on success
 | 
			
		||||
 */
 | 
			
		||||
bool flipper_format_write_hex_uint64(
 | 
			
		||||
    FlipperFormat* flipper_format,
 | 
			
		||||
    const char* key,
 | 
			
		||||
    const uint64_t* data,
 | 
			
		||||
    const uint16_t data_size);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Read array of uint32 by key
 | 
			
		||||
 * @param flipper_format Pointer to a FlipperFormat instance
 | 
			
		||||
 | 
			
		||||
@ -287,6 +287,11 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa
 | 
			
		||||
                    const uint32_t* data = write_data->data;
 | 
			
		||||
                    string_printf(value, "%" PRId32, data[i]);
 | 
			
		||||
                }; break;
 | 
			
		||||
                case FlipperStreamValueHexUint64: {
 | 
			
		||||
                    const uint64_t* data = write_data->data;
 | 
			
		||||
                    string_printf(
 | 
			
		||||
                        value, "%08lX%08lX", (uint32_t)(data[i] >> 32), (uint32_t)data[i]);
 | 
			
		||||
                }; break;
 | 
			
		||||
                case FlipperStreamValueBool: {
 | 
			
		||||
                    const bool* data = write_data->data;
 | 
			
		||||
                    string_printf(value, data[i] ? "true" : "false");
 | 
			
		||||
@ -380,6 +385,14 @@ bool flipper_format_stream_read_value_line(
 | 
			
		||||
                        uint32_t* data = _data;
 | 
			
		||||
                        scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]);
 | 
			
		||||
                    }; break;
 | 
			
		||||
                    case FlipperStreamValueHexUint64: {
 | 
			
		||||
                        uint64_t* data = _data;
 | 
			
		||||
                        if(string_size(value) >= 16) {
 | 
			
		||||
                            if(hex_chars_to_uint64(string_get_cstr(value), &data[i])) {
 | 
			
		||||
                                scan_values = 1;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }; break;
 | 
			
		||||
                    case FlipperStreamValueBool: {
 | 
			
		||||
                        bool* data = _data;
 | 
			
		||||
                        data[i] = !string_cmpi_str(value, "true");
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ typedef enum {
 | 
			
		||||
    FlipperStreamValueFloat,
 | 
			
		||||
    FlipperStreamValueInt32,
 | 
			
		||||
    FlipperStreamValueUint32,
 | 
			
		||||
    FlipperStreamValueHexUint64,
 | 
			
		||||
    FlipperStreamValueBool,
 | 
			
		||||
} FlipperStreamValue;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -95,6 +95,11 @@ C_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*/*.c)
 | 
			
		||||
CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*.cpp)
 | 
			
		||||
CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*/*.cpp)
 | 
			
		||||
 | 
			
		||||
# Digital signal
 | 
			
		||||
CFLAGS			+= -I$(LIB_DIR)/digital_signal
 | 
			
		||||
C_SOURCES		+= $(wildcard $(LIB_DIR)/digital_signal/*.c)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# USB Stack
 | 
			
		||||
CFLAGS			+= -I$(LIB_DIR)/libusb_stm32/inc
 | 
			
		||||
C_SOURCES		+= $(LIB_DIR)/libusb_stm32/src/usbd_stm32wb55_devfs.c
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,7 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
 | 
			
		||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
 | 
			
		||||
    furi_assert(crypto1);
 | 
			
		||||
    uint32_t out = 0;
 | 
			
		||||
    for(uint8_t i = 0; i < 32; i++) {
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted);
 | 
			
		||||
 | 
			
		||||
uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted);
 | 
			
		||||
 | 
			
		||||
uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
 | 
			
		||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
 | 
			
		||||
 | 
			
		||||
uint32_t crypto1_filter(uint32_t in);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
#include "mifare_classic.h"
 | 
			
		||||
#include "nfca.h"
 | 
			
		||||
#include "nfc_util.h"
 | 
			
		||||
#include <furi_hal_rtc.h>
 | 
			
		||||
 | 
			
		||||
// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
 | 
			
		||||
 | 
			
		||||
@ -10,6 +11,20 @@
 | 
			
		||||
#define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U)
 | 
			
		||||
#define MF_CLASSIC_READ_SECT_CMD (0x30)
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    MfClassicActionDataRead,
 | 
			
		||||
    MfClassicActionDataWrite,
 | 
			
		||||
    MfClassicActionDataInc,
 | 
			
		||||
    MfClassicActionDataDec,
 | 
			
		||||
 | 
			
		||||
    MfClassicActionKeyARead,
 | 
			
		||||
    MfClassicActionKeyAWrite,
 | 
			
		||||
    MfClassicActionKeyBRead,
 | 
			
		||||
    MfClassicActionKeyBWrite,
 | 
			
		||||
    MfClassicActionACRead,
 | 
			
		||||
    MfClassicActionACWrite,
 | 
			
		||||
} MfClassicAction;
 | 
			
		||||
 | 
			
		||||
static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
 | 
			
		||||
    furi_assert(sector < 40);
 | 
			
		||||
    if(sector < 32) {
 | 
			
		||||
@ -19,11 +34,31 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint8_t mf_classic_get_sector_by_block(uint8_t block) {
 | 
			
		||||
    if(block < 128) {
 | 
			
		||||
        return (block | 0x03) / 4;
 | 
			
		||||
    } else {
 | 
			
		||||
        return 32 + ((block | 0xf) - 32 * 4) / 16;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
 | 
			
		||||
    furi_assert(sector < 40);
 | 
			
		||||
    return sector < 32 ? 4 : 16;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint8_t mf_classic_get_sector_trailer(uint8_t block) {
 | 
			
		||||
    if(block < 128) {
 | 
			
		||||
        return block | 0x03;
 | 
			
		||||
    } else {
 | 
			
		||||
        return block | 0x0f;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool mf_classic_is_sector_trailer(uint8_t block) {
 | 
			
		||||
    return block == mf_classic_get_sector_trailer(block);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
 | 
			
		||||
    furi_assert(reader);
 | 
			
		||||
    if(reader->type == MfClassicType1k) {
 | 
			
		||||
@ -35,6 +70,132 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint16_t mf_classic_get_total_block_num(MfClassicType type) {
 | 
			
		||||
    if(type == MfClassicType1k) {
 | 
			
		||||
        return 64;
 | 
			
		||||
    } else if(type == MfClassicType4k) {
 | 
			
		||||
        return 256;
 | 
			
		||||
    } else {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool mf_classic_is_allowed_access_sector_trailer(
 | 
			
		||||
    MfClassicEmulator* emulator,
 | 
			
		||||
    uint8_t block_num,
 | 
			
		||||
    MfClassicKey key,
 | 
			
		||||
    MfClassicAction action) {
 | 
			
		||||
    uint8_t* sector_trailer = emulator->data.block[block_num].value;
 | 
			
		||||
    uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) |
 | 
			
		||||
                 ((sector_trailer[8] >> 7) & 0x01);
 | 
			
		||||
    switch(action) {
 | 
			
		||||
    case MfClassicActionKeyARead: {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionKeyAWrite: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) ||
 | 
			
		||||
            (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03)));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionKeyBRead: {
 | 
			
		||||
        return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionKeyBWrite: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) ||
 | 
			
		||||
            (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03)));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionACRead: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA) ||
 | 
			
		||||
            (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01)));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionACWrite: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && (AC == 0x01)) ||
 | 
			
		||||
            (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05)));
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool mf_classic_is_allowed_access_data_block(
 | 
			
		||||
    MfClassicEmulator* emulator,
 | 
			
		||||
    uint8_t block_num,
 | 
			
		||||
    MfClassicKey key,
 | 
			
		||||
    MfClassicAction action) {
 | 
			
		||||
    uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value;
 | 
			
		||||
 | 
			
		||||
    uint8_t sector_block;
 | 
			
		||||
    if(block_num <= 128) {
 | 
			
		||||
        sector_block = block_num & 0x03;
 | 
			
		||||
    } else {
 | 
			
		||||
        sector_block = (block_num & 0x0f) / 5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint8_t AC;
 | 
			
		||||
    switch(sector_block) {
 | 
			
		||||
    case 0x00: {
 | 
			
		||||
        AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) |
 | 
			
		||||
             ((sector_trailer[8] >> 4) & 0x01);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case 0x01: {
 | 
			
		||||
        AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) |
 | 
			
		||||
             ((sector_trailer[8] >> 5) & 0x01);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case 0x02: {
 | 
			
		||||
        AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) |
 | 
			
		||||
             ((sector_trailer[8] >> 6) & 0x01);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch(action) {
 | 
			
		||||
    case MfClassicActionDataRead: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) ||
 | 
			
		||||
            (key == MfClassicKeyB && !(AC == 0x07)));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionDataWrite: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && (AC == 0x00)) ||
 | 
			
		||||
            (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03)));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionDataInc: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && (AC == 0x00)) ||
 | 
			
		||||
            (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06)));
 | 
			
		||||
    }
 | 
			
		||||
    case MfClassicActionDataDec: {
 | 
			
		||||
        return (
 | 
			
		||||
            (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) ||
 | 
			
		||||
            (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01)));
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool mf_classic_is_allowed_access(
 | 
			
		||||
    MfClassicEmulator* emulator,
 | 
			
		||||
    uint8_t block_num,
 | 
			
		||||
    MfClassicKey key,
 | 
			
		||||
    MfClassicAction action) {
 | 
			
		||||
    if(mf_classic_is_sector_trailer(block_num)) {
 | 
			
		||||
        return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action);
 | 
			
		||||
    } else {
 | 
			
		||||
        return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
 | 
			
		||||
    UNUSED(ATQA1);
 | 
			
		||||
    if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) {
 | 
			
		||||
@ -120,7 +281,7 @@ static bool mf_classic_auth(
 | 
			
		||||
        tx_rx->tx_data[1] = block;
 | 
			
		||||
        tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc;
 | 
			
		||||
        tx_rx->tx_bits = 2 * 8;
 | 
			
		||||
        if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break;
 | 
			
		||||
        if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break;
 | 
			
		||||
 | 
			
		||||
        uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4);
 | 
			
		||||
        crypto1_init(crypto, key);
 | 
			
		||||
@ -142,7 +303,7 @@ static bool mf_classic_auth(
 | 
			
		||||
        }
 | 
			
		||||
        tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
 | 
			
		||||
        tx_rx->tx_bits = 8 * 8;
 | 
			
		||||
        if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break;
 | 
			
		||||
        if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break;
 | 
			
		||||
        if(tx_rx->rx_bits == 32) {
 | 
			
		||||
            crypto1_word(crypto, 0, 0);
 | 
			
		||||
            auth_success = true;
 | 
			
		||||
@ -296,6 +457,8 @@ uint8_t mf_classic_read_card(
 | 
			
		||||
 | 
			
		||||
    uint8_t sectors_read = 0;
 | 
			
		||||
    data->type = reader->type;
 | 
			
		||||
    data->key_a_mask = 0;
 | 
			
		||||
    data->key_b_mask = 0;
 | 
			
		||||
    MfClassicSector temp_sector = {};
 | 
			
		||||
    for(uint8_t i = 0; i < reader->sectors_to_read; i++) {
 | 
			
		||||
        if(mf_classic_read_sector(
 | 
			
		||||
@ -305,9 +468,279 @@ uint8_t mf_classic_read_card(
 | 
			
		||||
            for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
 | 
			
		||||
                data->block[first_block + j] = temp_sector.block[j];
 | 
			
		||||
            }
 | 
			
		||||
            if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) {
 | 
			
		||||
                data->key_a_mask |= 1 << reader->sector_reader[i].sector_num;
 | 
			
		||||
            }
 | 
			
		||||
            if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) {
 | 
			
		||||
                data->key_b_mask |= 1 << reader->sector_reader[i].sector_num;
 | 
			
		||||
            }
 | 
			
		||||
            sectors_read++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return sectors_read;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void mf_crypto1_decrypt(
 | 
			
		||||
    Crypto1* crypto,
 | 
			
		||||
    uint8_t* encrypted_data,
 | 
			
		||||
    uint16_t encrypted_data_bits,
 | 
			
		||||
    uint8_t* decrypted_data) {
 | 
			
		||||
    if(encrypted_data_bits < 8) {
 | 
			
		||||
        uint8_t decrypted_byte = 0;
 | 
			
		||||
        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0;
 | 
			
		||||
        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1;
 | 
			
		||||
        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2;
 | 
			
		||||
        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3;
 | 
			
		||||
        decrypted_data[0] = decrypted_byte;
 | 
			
		||||
    } else {
 | 
			
		||||
        for(size_t i = 0; i < encrypted_data_bits / 8; i++) {
 | 
			
		||||
            decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void mf_crypto1_encrypt(
 | 
			
		||||
    Crypto1* crypto,
 | 
			
		||||
    uint8_t* keystream,
 | 
			
		||||
    uint8_t* plain_data,
 | 
			
		||||
    uint16_t plain_data_bits,
 | 
			
		||||
    uint8_t* encrypted_data,
 | 
			
		||||
    uint8_t* encrypted_parity) {
 | 
			
		||||
    if(plain_data_bits < 8) {
 | 
			
		||||
        encrypted_data[0] = 0;
 | 
			
		||||
        for(size_t i = 0; i < plain_data_bits; i++) {
 | 
			
		||||
            encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        memset(encrypted_parity, 0, plain_data_bits / 8 + 1);
 | 
			
		||||
        for(uint8_t i = 0; i < plain_data_bits / 8; i++) {
 | 
			
		||||
            encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
 | 
			
		||||
                                plain_data[i];
 | 
			
		||||
            encrypted_parity[i / 8] |=
 | 
			
		||||
                (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01)
 | 
			
		||||
                 << (7 - (i & 0x0007)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) {
 | 
			
		||||
    furi_assert(emulator);
 | 
			
		||||
    furi_assert(tx_rx);
 | 
			
		||||
    bool command_processed = false;
 | 
			
		||||
    bool is_encrypted = false;
 | 
			
		||||
    uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE];
 | 
			
		||||
    MfClassicKey access_key = MfClassicKeyA;
 | 
			
		||||
 | 
			
		||||
    // Read command
 | 
			
		||||
    while(!command_processed) {
 | 
			
		||||
        if(!is_encrypted) {
 | 
			
		||||
            // Read first frame
 | 
			
		||||
            tx_rx->tx_bits = 0;
 | 
			
		||||
            tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
 | 
			
		||||
        }
 | 
			
		||||
        if(!furi_hal_nfc_tx_rx(tx_rx, 300)) {
 | 
			
		||||
            FURI_LOG_D(
 | 
			
		||||
                TAG, "Error in tx rx. Tx :%d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if(!is_encrypted) {
 | 
			
		||||
            memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8);
 | 
			
		||||
        } else {
 | 
			
		||||
            mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
 | 
			
		||||
        }
 | 
			
		||||
        // TODO Check crc
 | 
			
		||||
 | 
			
		||||
        if(plain_data[0] == 0x50 && plain_data[1] == 00) {
 | 
			
		||||
            FURI_LOG_T(TAG, "Halt received");
 | 
			
		||||
            command_processed = true;
 | 
			
		||||
            break;
 | 
			
		||||
        } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) {
 | 
			
		||||
            uint8_t block = plain_data[1];
 | 
			
		||||
            uint64_t key = 0;
 | 
			
		||||
            uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block);
 | 
			
		||||
            MfClassicSectorTrailer* sector_trailer =
 | 
			
		||||
                (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value;
 | 
			
		||||
            if(plain_data[0] == 0x61) {
 | 
			
		||||
                key = nfc_util_bytes2num(sector_trailer->key_b, 6);
 | 
			
		||||
                access_key = MfClassicKeyA;
 | 
			
		||||
            } else {
 | 
			
		||||
                key = nfc_util_bytes2num(sector_trailer->key_a, 6);
 | 
			
		||||
                access_key = MfClassicKeyB;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uint32_t nonce = prng_successor(DWT->CYCCNT, 32);
 | 
			
		||||
            uint8_t nt[4];
 | 
			
		||||
            uint8_t nt_keystream[4];
 | 
			
		||||
            nfc_util_num2bytes(nonce, 4, nt);
 | 
			
		||||
            nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream);
 | 
			
		||||
            crypto1_init(&emulator->crypto, key);
 | 
			
		||||
            if(!is_encrypted) {
 | 
			
		||||
                crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0);
 | 
			
		||||
                memcpy(tx_rx->tx_data, nt, sizeof(nt));
 | 
			
		||||
                tx_rx->tx_bits = sizeof(nt) * 8;
 | 
			
		||||
                tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxRaw;
 | 
			
		||||
            } else {
 | 
			
		||||
                mf_crypto1_encrypt(
 | 
			
		||||
                    &emulator->crypto,
 | 
			
		||||
                    nt_keystream,
 | 
			
		||||
                    nt,
 | 
			
		||||
                    sizeof(nt) * 8,
 | 
			
		||||
                    tx_rx->tx_data,
 | 
			
		||||
                    tx_rx->tx_parity);
 | 
			
		||||
                tx_rx->tx_bits = sizeof(nt) * 8;
 | 
			
		||||
                tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
 | 
			
		||||
            }
 | 
			
		||||
            if(!furi_hal_nfc_tx_rx(tx_rx, 500)) {
 | 
			
		||||
                FURI_LOG_E(TAG, "Error in NT exchange");
 | 
			
		||||
                command_processed = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(tx_rx->rx_bits != 64) {
 | 
			
		||||
                FURI_LOG_W(TAG, "Incorrect nr + ar");
 | 
			
		||||
                command_processed = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check if we store valid key
 | 
			
		||||
            if(access_key == MfClassicKeyA) {
 | 
			
		||||
                if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) ==
 | 
			
		||||
                   0) {
 | 
			
		||||
                    FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            } else if(access_key == MfClassicKeyB) {
 | 
			
		||||
                if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) ==
 | 
			
		||||
                   0) {
 | 
			
		||||
                    FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4);
 | 
			
		||||
            uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4);
 | 
			
		||||
            crypto1_word(&emulator->crypto, nr, 1);
 | 
			
		||||
            uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0);
 | 
			
		||||
            if(cardRr != prng_successor(nonce, 64)) {
 | 
			
		||||
                FURI_LOG_T(TAG, "Wrong AUTH! %08X != %08X", cardRr, prng_successor(nonce, 64));
 | 
			
		||||
                // Don't send NACK, as tag don't send it
 | 
			
		||||
                command_processed = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uint32_t ans = prng_successor(nonce, 96);
 | 
			
		||||
            uint8_t responce[4] = {};
 | 
			
		||||
            nfc_util_num2bytes(ans, 4, responce);
 | 
			
		||||
            mf_crypto1_encrypt(
 | 
			
		||||
                &emulator->crypto,
 | 
			
		||||
                NULL,
 | 
			
		||||
                responce,
 | 
			
		||||
                sizeof(responce) * 8,
 | 
			
		||||
                tx_rx->tx_data,
 | 
			
		||||
                tx_rx->tx_parity);
 | 
			
		||||
            tx_rx->tx_bits = sizeof(responce) * 8;
 | 
			
		||||
            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
 | 
			
		||||
 | 
			
		||||
            is_encrypted = true;
 | 
			
		||||
        } else if(is_encrypted && plain_data[0] == 0x30) {
 | 
			
		||||
            uint8_t block = plain_data[1];
 | 
			
		||||
            uint8_t block_data[18] = {};
 | 
			
		||||
            memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
 | 
			
		||||
            if(mf_classic_is_sector_trailer(block)) {
 | 
			
		||||
                if(!mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionKeyARead)) {
 | 
			
		||||
                    memset(block_data, 0, 6);
 | 
			
		||||
                }
 | 
			
		||||
                if(!mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionKeyBRead)) {
 | 
			
		||||
                    memset(&block_data[10], 0, 6);
 | 
			
		||||
                }
 | 
			
		||||
                if(!mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionACRead)) {
 | 
			
		||||
                    memset(&block_data[6], 0, 4);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if(!mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionDataRead)) {
 | 
			
		||||
                    memset(block_data, 0, 16);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            nfca_append_crc16(block_data, 16);
 | 
			
		||||
 | 
			
		||||
            mf_crypto1_encrypt(
 | 
			
		||||
                &emulator->crypto,
 | 
			
		||||
                NULL,
 | 
			
		||||
                block_data,
 | 
			
		||||
                sizeof(block_data) * 8,
 | 
			
		||||
                tx_rx->tx_data,
 | 
			
		||||
                tx_rx->tx_parity);
 | 
			
		||||
            tx_rx->tx_bits = 18 * 8;
 | 
			
		||||
            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
 | 
			
		||||
        } else if(is_encrypted && plain_data[0] == 0xA0) {
 | 
			
		||||
            uint8_t block = plain_data[1];
 | 
			
		||||
            if(block > mf_classic_get_total_block_num(emulator->data.type)) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            // Send ACK
 | 
			
		||||
            uint8_t ack = 0x0A;
 | 
			
		||||
            mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
 | 
			
		||||
            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
 | 
			
		||||
            tx_rx->tx_bits = 4;
 | 
			
		||||
 | 
			
		||||
            if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
 | 
			
		||||
            if(tx_rx->rx_bits != 18 * 8) break;
 | 
			
		||||
 | 
			
		||||
            mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
 | 
			
		||||
            uint8_t block_data[16] = {};
 | 
			
		||||
            memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
 | 
			
		||||
            if(mf_classic_is_sector_trailer(block)) {
 | 
			
		||||
                if(mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionKeyAWrite)) {
 | 
			
		||||
                    memcpy(block_data, plain_data, 6);
 | 
			
		||||
                }
 | 
			
		||||
                if(mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionKeyBWrite)) {
 | 
			
		||||
                    memcpy(&block_data[10], &plain_data[10], 6);
 | 
			
		||||
                }
 | 
			
		||||
                if(mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionACWrite)) {
 | 
			
		||||
                    memcpy(&block_data[6], &plain_data[6], 4);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if(mf_classic_is_allowed_access(
 | 
			
		||||
                       emulator, block, access_key, MfClassicActionDataWrite)) {
 | 
			
		||||
                    memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) {
 | 
			
		||||
                memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE);
 | 
			
		||||
                emulator->data_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
            // Send ACK
 | 
			
		||||
            ack = 0x0A;
 | 
			
		||||
            mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
 | 
			
		||||
            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
 | 
			
		||||
            tx_rx->tx_bits = 4;
 | 
			
		||||
        } else {
 | 
			
		||||
            // Unknown command
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(!command_processed) {
 | 
			
		||||
        // Send NACK
 | 
			
		||||
        uint8_t nack = 0x04;
 | 
			
		||||
        if(is_encrypted) {
 | 
			
		||||
            mf_crypto1_encrypt(
 | 
			
		||||
                &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
 | 
			
		||||
        } else {
 | 
			
		||||
            tx_rx->tx_data[0] = nack;
 | 
			
		||||
        }
 | 
			
		||||
        tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
 | 
			
		||||
        tx_rx->tx_bits = 4;
 | 
			
		||||
        furi_hal_nfc_tx_rx(tx_rx, 300);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@
 | 
			
		||||
#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16)
 | 
			
		||||
 | 
			
		||||
#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF)
 | 
			
		||||
#define MF_CLASSIC_MAX_DATA_SIZE (16)
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    MfClassicType1k,
 | 
			
		||||
@ -41,6 +42,8 @@ typedef struct {
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    MfClassicType type;
 | 
			
		||||
    uint64_t key_a_mask;
 | 
			
		||||
    uint64_t key_b_mask;
 | 
			
		||||
    MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
 | 
			
		||||
} MfClassicData;
 | 
			
		||||
 | 
			
		||||
@ -65,6 +68,13 @@ typedef struct {
 | 
			
		||||
    MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX];
 | 
			
		||||
} MfClassicReader;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint32_t cuid;
 | 
			
		||||
    Crypto1 crypto;
 | 
			
		||||
    MfClassicData data;
 | 
			
		||||
    bool data_changed;
 | 
			
		||||
} MfClassicEmulator;
 | 
			
		||||
 | 
			
		||||
bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
 | 
			
		||||
 | 
			
		||||
bool mf_classic_get_type(
 | 
			
		||||
@ -100,3 +110,5 @@ uint8_t mf_classic_read_card(
 | 
			
		||||
    FuriHalNfcTxRxContext* tx_rx,
 | 
			
		||||
    MfClassicReader* reader,
 | 
			
		||||
    MfClassicData* data);
 | 
			
		||||
 | 
			
		||||
bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,17 @@
 | 
			
		||||
#include "nfca.h"
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
 | 
			
		||||
#define NFCA_CMD_RATS (0xE0U)
 | 
			
		||||
 | 
			
		||||
#define NFCA_CRC_INIT (0x6363)
 | 
			
		||||
 | 
			
		||||
#define NFCA_F_SIG (13560000.0)
 | 
			
		||||
#define NFCA_T_SIG (1.0 / NFCA_F_SIG)
 | 
			
		||||
 | 
			
		||||
#define NFCA_SIGNAL_MAX_EDGES (1350)
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t cmd;
 | 
			
		||||
    uint8_t param;
 | 
			
		||||
@ -53,3 +59,81 @@ bool nfca_emulation_handler(
 | 
			
		||||
 | 
			
		||||
    return sleep;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nfca_add_bit(DigitalSignal* signal, bool bit) {
 | 
			
		||||
    if(bit) {
 | 
			
		||||
        signal->start_level = true;
 | 
			
		||||
        for(size_t i = 0; i < 7; i++) {
 | 
			
		||||
            signal->edge_timings[i] = 8 * NFCA_T_SIG;
 | 
			
		||||
        }
 | 
			
		||||
        signal->edge_timings[7] = 9 * 8 * NFCA_T_SIG;
 | 
			
		||||
        signal->edge_cnt = 8;
 | 
			
		||||
    } else {
 | 
			
		||||
        signal->start_level = false;
 | 
			
		||||
        signal->edge_timings[0] = 8 * 8 * NFCA_T_SIG;
 | 
			
		||||
        for(size_t i = 1; i < 9; i++) {
 | 
			
		||||
            signal->edge_timings[i] = 8 * NFCA_T_SIG;
 | 
			
		||||
        }
 | 
			
		||||
        signal->edge_cnt = 9;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) {
 | 
			
		||||
    for(uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
        if(byte & (1 << i)) {
 | 
			
		||||
            digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
 | 
			
		||||
        } else {
 | 
			
		||||
            digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if(parity) {
 | 
			
		||||
        digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
 | 
			
		||||
    } else {
 | 
			
		||||
        digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NfcaSignal* nfca_signal_alloc() {
 | 
			
		||||
    NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal));
 | 
			
		||||
    nfca_signal->one = digital_signal_alloc(10);
 | 
			
		||||
    nfca_signal->zero = digital_signal_alloc(10);
 | 
			
		||||
    nfca_add_bit(nfca_signal->one, true);
 | 
			
		||||
    nfca_add_bit(nfca_signal->zero, false);
 | 
			
		||||
    nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES);
 | 
			
		||||
 | 
			
		||||
    return nfca_signal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfca_signal_free(NfcaSignal* nfca_signal) {
 | 
			
		||||
    furi_assert(nfca_signal);
 | 
			
		||||
 | 
			
		||||
    digital_signal_free(nfca_signal->one);
 | 
			
		||||
    digital_signal_free(nfca_signal->zero);
 | 
			
		||||
    digital_signal_free(nfca_signal->tx_signal);
 | 
			
		||||
    free(nfca_signal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) {
 | 
			
		||||
    furi_assert(nfca_signal);
 | 
			
		||||
    furi_assert(data);
 | 
			
		||||
    furi_assert(parity);
 | 
			
		||||
 | 
			
		||||
    nfca_signal->tx_signal->edge_cnt = 0;
 | 
			
		||||
    nfca_signal->tx_signal->start_level = true;
 | 
			
		||||
    // Start of frame
 | 
			
		||||
    digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
 | 
			
		||||
 | 
			
		||||
    if(bits < 8) {
 | 
			
		||||
        for(size_t i = 0; i < bits; i++) {
 | 
			
		||||
            if(FURI_BIT(data[0], i)) {
 | 
			
		||||
                digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
 | 
			
		||||
            } else {
 | 
			
		||||
                digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        for(size_t i = 0; i < bits / 8; i++) {
 | 
			
		||||
            nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07))));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,14 @@
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <lib/digital_signal/digital_signal.h>
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    DigitalSignal* one;
 | 
			
		||||
    DigitalSignal* zero;
 | 
			
		||||
    DigitalSignal* tx_signal;
 | 
			
		||||
} NfcaSignal;
 | 
			
		||||
 | 
			
		||||
uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len);
 | 
			
		||||
 | 
			
		||||
void nfca_append_crc16(uint8_t* buff, uint16_t len);
 | 
			
		||||
@ -12,3 +20,9 @@ bool nfca_emulation_handler(
 | 
			
		||||
    uint16_t buff_rx_len,
 | 
			
		||||
    uint8_t* buff_tx,
 | 
			
		||||
    uint16_t* buff_tx_len);
 | 
			
		||||
 | 
			
		||||
NfcaSignal* nfca_signal_alloc();
 | 
			
		||||
 | 
			
		||||
void nfca_signal_free(NfcaSignal* nfca_signal);
 | 
			
		||||
 | 
			
		||||
void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity);
 | 
			
		||||
 | 
			
		||||
@ -26,3 +26,14 @@ bool hex_chars_to_uint8(char hi, char low, uint8_t* value) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool hex_chars_to_uint64(const char* value_str, uint64_t* value) {
 | 
			
		||||
    uint8_t* _value = (uint8_t*)value;
 | 
			
		||||
    bool parse_success = false;
 | 
			
		||||
 | 
			
		||||
    for(uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
        parse_success = hex_chars_to_uint8(value_str[i * 2], value_str[i * 2 + 1], &_value[7 - i]);
 | 
			
		||||
        if(!parse_success) break;
 | 
			
		||||
    }
 | 
			
		||||
    return parse_success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,23 +6,31 @@
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert ASCII hex value to nibble 
 | 
			
		||||
/** Convert ASCII hex value to nibble
 | 
			
		||||
 * @param c         ASCII character
 | 
			
		||||
 * @param nibble    nibble pointer, output
 | 
			
		||||
 *
 | 
			
		||||
 * @return          bool conversion status
 | 
			
		||||
 */
 | 
			
		||||
bool hex_char_to_hex_nibble(char c, uint8_t* nibble);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert ASCII hex values to byte
 | 
			
		||||
/** Convert ASCII hex values to byte
 | 
			
		||||
 * @param hi        hi nibble text
 | 
			
		||||
 * @param low       low nibble text
 | 
			
		||||
 * @param value     output value
 | 
			
		||||
 *
 | 
			
		||||
 * @return          bool conversion status
 | 
			
		||||
 */
 | 
			
		||||
bool hex_chars_to_uint8(char hi, char low, uint8_t* value);
 | 
			
		||||
 | 
			
		||||
/** Convert ASCII hex values to uint64_t
 | 
			
		||||
 * @param value_str ASCII 64 bi data
 | 
			
		||||
 * @param value     output value
 | 
			
		||||
 *
 | 
			
		||||
 * @return          bool conversion status
 | 
			
		||||
 */
 | 
			
		||||
bool hex_chars_to_uint64(const char* value_str, uint64_t* value);
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user