mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-11-29 22:49:55 +03:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
commit
7f05a224be
@ -0,0 +1,40 @@
|
|||||||
|
Filetype: Flipper NFC device
|
||||||
|
Version: 4
|
||||||
|
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
|
||||||
|
Device type: FeliCa
|
||||||
|
# UID is common for all formats
|
||||||
|
UID: 29 9F FA 53 AB 75 87 6E
|
||||||
|
# FeliCa specific data
|
||||||
|
Data format version: 1
|
||||||
|
Manufacture id: 29 9F FA 53 AB 75 87 6E
|
||||||
|
Manufacture parameter: 57 4E 10 2A 94 16 BC 8E
|
||||||
|
Blocks total: 28
|
||||||
|
Blocks read: 28
|
||||||
|
Block 0: 00 00 DE AD BE AF 00 00 00 00 00 00 00 00 DE AD BE AF
|
||||||
|
Block 1: 00 00 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
|
||||||
|
Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 7: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 9: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 12: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 14: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
|
||||||
|
Block 15: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 16: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 17: 00 00 29 9F FA 53 AB 75 87 6E 57 4E 10 2A 94 16 BC 8E
|
||||||
|
Block 18: 00 00 29 9F FA 53 AB 75 87 6E 00 F1 00 00 00 01 43 00
|
||||||
|
Block 19: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 20: 00 00 88 B4 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 21: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 22: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
|
||||||
|
Block 23: 00 00 FF FF FF 00 FF 00 10 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 24: 00 00 24 FE FF 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 25: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 26: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
Block 27: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
@ -12,6 +12,8 @@
|
|||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||||
|
#include <nfc/protocols/felica/felica.h>
|
||||||
|
#include <nfc/protocols/felica/felica_poller_sync.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
|
||||||
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
|
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
|
||||||
#include <nfc/protocols/slix/slix.h>
|
#include <nfc/protocols/slix/slix.h>
|
||||||
@ -646,6 +648,56 @@ MU_TEST(mf_classic_dict_test) {
|
|||||||
"Remove test dict failed");
|
"Remove test dict failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FelicaError
|
||||||
|
felica_do_request_response(FelicaData* felica_data, const FelicaCardKey* card_key) {
|
||||||
|
NfcDeviceData* nfc_device = nfc_device_alloc();
|
||||||
|
|
||||||
|
FelicaError error = FelicaErrorNone;
|
||||||
|
if(!nfc_device_load(nfc_device, EXT_PATH("unit_tests/nfc/Felica.nfc"))) {
|
||||||
|
error = FelicaErrorNotPresent;
|
||||||
|
} else {
|
||||||
|
Nfc* poller = nfc_alloc();
|
||||||
|
Nfc* listener = nfc_alloc();
|
||||||
|
NfcListener* felica_listener = nfc_listener_alloc(
|
||||||
|
listener, NfcProtocolFelica, nfc_device_get_data(nfc_device, NfcProtocolFelica));
|
||||||
|
nfc_listener_start(felica_listener, NULL, NULL);
|
||||||
|
|
||||||
|
error = felica_poller_sync_read(poller, felica_data, card_key);
|
||||||
|
|
||||||
|
nfc_listener_stop(felica_listener);
|
||||||
|
nfc_listener_free(felica_listener);
|
||||||
|
|
||||||
|
nfc_free(listener);
|
||||||
|
nfc_free(poller);
|
||||||
|
}
|
||||||
|
|
||||||
|
nfc_device_free(nfc_device);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST(felica_read) {
|
||||||
|
FelicaData* felica_data = felica_alloc();
|
||||||
|
FelicaError error = felica_do_request_response(felica_data, NULL);
|
||||||
|
mu_assert(error == FelicaErrorNone, "felica_poller() failed");
|
||||||
|
mu_assert(felica_data->data.fs.spad[4].SF1 == 0x01, "block[4].SF1 != 0x01");
|
||||||
|
mu_assert(felica_data->data.fs.spad[4].SF2 == 0xB1, "block[4].SF2 != 0xB1");
|
||||||
|
|
||||||
|
felica_free(felica_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST(felica_read_auth) {
|
||||||
|
FelicaData* felica_data = felica_alloc();
|
||||||
|
FelicaCardKey card_key;
|
||||||
|
memset(card_key.data, 0xFF, FELICA_DATA_BLOCK_SIZE);
|
||||||
|
|
||||||
|
FelicaError error = felica_do_request_response(felica_data, &card_key);
|
||||||
|
mu_assert(error == FelicaErrorNone, "felica_poller() failed");
|
||||||
|
mu_assert(felica_data->data.fs.spad[4].SF1 == 0x00, "block[4].SF1 != 0x00");
|
||||||
|
mu_assert(felica_data->data.fs.spad[4].SF2 == 0x00, "block[4].SF2 != 0x00");
|
||||||
|
|
||||||
|
felica_free(felica_data);
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST(slix_file_with_capabilities_test) {
|
MU_TEST(slix_file_with_capabilities_test) {
|
||||||
NfcDevice* nfc_device_missed_cap = nfc_device_alloc();
|
NfcDevice* nfc_device_missed_cap = nfc_device_alloc();
|
||||||
mu_assert(
|
mu_assert(
|
||||||
@ -807,6 +859,8 @@ MU_TEST_SUITE(nfc) {
|
|||||||
MU_RUN_TEST(mf_classic_value_block);
|
MU_RUN_TEST(mf_classic_value_block);
|
||||||
MU_RUN_TEST(mf_classic_send_frame_test);
|
MU_RUN_TEST(mf_classic_send_frame_test);
|
||||||
MU_RUN_TEST(mf_classic_dict_test);
|
MU_RUN_TEST(mf_classic_dict_test);
|
||||||
|
MU_RUN_TEST(felica_read);
|
||||||
|
MU_RUN_TEST(felica_read_auth);
|
||||||
|
|
||||||
MU_RUN_TEST(slix_file_with_capabilities_test);
|
MU_RUN_TEST(slix_file_with_capabilities_test);
|
||||||
MU_RUN_TEST(slix_set_password_default_cap_correct_pass);
|
MU_RUN_TEST(slix_set_password_default_cap_correct_pass);
|
||||||
|
@ -26,12 +26,6 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
|
|||||||
widget_add_text_scroll_element(
|
widget_add_text_scroll_element(
|
||||||
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str));
|
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str));
|
||||||
|
|
||||||
widget_add_button_element(
|
|
||||||
instance->widget,
|
|
||||||
GuiButtonTypeRight,
|
|
||||||
"More",
|
|
||||||
nfc_protocol_support_common_widget_callback,
|
|
||||||
instance);
|
|
||||||
furi_string_free(temp_str);
|
furi_string_free(temp_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +171,7 @@ static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEv
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||||
.features = NfcProtocolFeatureEmulateUid,
|
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
|
||||||
|
|
||||||
.scene_info =
|
.scene_info =
|
||||||
{
|
{
|
||||||
|
@ -52,6 +52,7 @@ typedef enum {
|
|||||||
SubGhzRxKeyStateAddKey,
|
SubGhzRxKeyStateAddKey,
|
||||||
SubGhzRxKeyStateExit,
|
SubGhzRxKeyStateExit,
|
||||||
SubGhzRxKeyStateRAWLoad,
|
SubGhzRxKeyStateRAWLoad,
|
||||||
|
SubGhzRxKeyStateRAWMore,
|
||||||
SubGhzRxKeyStateRAWSave,
|
SubGhzRxKeyStateRAWSave,
|
||||||
} SubGhzRxKeyState;
|
} SubGhzRxKeyState;
|
||||||
|
|
||||||
|
@ -66,7 +66,13 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
if(event.event == SubGhzCustomEventSceneDeleteRAW) {
|
if(event.event == SubGhzCustomEventSceneDeleteRAW) {
|
||||||
furi_string_set(subghz->file_path_tmp, subghz->file_path);
|
furi_string_set(subghz->file_path_tmp, subghz->file_path);
|
||||||
if(subghz_delete_file(subghz)) {
|
if(subghz_delete_file(subghz)) {
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess);
|
if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateRAWLoad) {
|
||||||
|
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||||
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess);
|
||||||
|
} else {
|
||||||
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
scene_manager_search_and_switch_to_previous_scene(
|
scene_manager_search_and_switch_to_previous_scene(
|
||||||
subghz->scene_manager, SubGhzSceneStart);
|
subghz->scene_manager, SubGhzSceneStart);
|
||||||
|
@ -80,6 +80,7 @@ void subghz_scene_read_raw_on_enter(void* context) {
|
|||||||
subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", threshold_rssi);
|
subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", threshold_rssi);
|
||||||
break;
|
break;
|
||||||
case SubGhzRxKeyStateRAWLoad:
|
case SubGhzRxKeyStateRAWLoad:
|
||||||
|
case SubGhzRxKeyStateRAWMore:
|
||||||
path_extract_filename(subghz->file_path, file_name, true);
|
path_extract_filename(subghz->file_path, file_name, true);
|
||||||
subghz_read_raw_set_status(
|
subghz_read_raw_set_status(
|
||||||
subghz->subghz_read_raw,
|
subghz->subghz_read_raw,
|
||||||
@ -101,7 +102,8 @@ void subghz_scene_read_raw_on_enter(void* context) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) {
|
if((subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateBack) &&
|
||||||
|
(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateRAWLoad)) {
|
||||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||||
|
|
||||||
if(furi_string_empty(file_name)) {
|
if(furi_string_empty(file_name)) {
|
||||||
@ -198,7 +200,9 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
if(subghz_scene_read_raw_update_filename(subghz)) {
|
if(subghz_scene_read_raw_update_filename(subghz)) {
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
|
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
|
||||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWLoad);
|
if(subghz_rx_key_state_get(subghz) != SubGhzRxKeyStateRAWLoad) {
|
||||||
|
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateRAWMore);
|
||||||
|
}
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <lib/toolbox/md5_calc.h>
|
#include <lib/toolbox/md5_calc.h>
|
||||||
#include <lib/toolbox/path.h>
|
#include <lib/toolbox/path.h>
|
||||||
#include <update_util/lfs_backup.h>
|
#include <update_util/lfs_backup.h>
|
||||||
|
#include <toolbox/tar/tar_archive.h>
|
||||||
|
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <storage.pb.h>
|
#include <storage.pb.h>
|
||||||
@ -50,7 +51,6 @@ static void rpc_system_storage_reset_state(
|
|||||||
if(rpc_storage->state == RpcStorageStateWriting) {
|
if(rpc_storage->state == RpcStorageStateWriting) {
|
||||||
storage_file_close(rpc_storage->file);
|
storage_file_close(rpc_storage->file);
|
||||||
storage_file_free(rpc_storage->file);
|
storage_file_free(rpc_storage->file);
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc_storage->state = RpcStorageStateIdle;
|
rpc_storage->state = RpcStorageStateIdle;
|
||||||
@ -118,10 +118,8 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex
|
|||||||
PB_Main* response = malloc(sizeof(PB_Main));
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
response->command_id = request->command_id;
|
response->command_id = request->command_id;
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
|
||||||
|
|
||||||
FS_Error error = storage_common_fs_info(
|
FS_Error error = storage_common_fs_info(
|
||||||
fs_api,
|
rpc_storage->api,
|
||||||
request->content.storage_info_request.path,
|
request->content.storage_info_request.path,
|
||||||
&response->content.storage_info_response.total_space,
|
&response->content.storage_info_response.total_space,
|
||||||
&response->content.storage_info_response.free_space);
|
&response->content.storage_info_response.free_space);
|
||||||
@ -135,7 +133,6 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex
|
|||||||
|
|
||||||
rpc_send_and_release(session, response);
|
rpc_send_and_release(session, response);
|
||||||
free(response);
|
free(response);
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_storage_timestamp_process(const PB_Main* request, void* context) {
|
static void rpc_system_storage_timestamp_process(const PB_Main* request, void* context) {
|
||||||
@ -154,11 +151,9 @@ static void rpc_system_storage_timestamp_process(const PB_Main* request, void* c
|
|||||||
PB_Main* response = malloc(sizeof(PB_Main));
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
response->command_id = request->command_id;
|
response->command_id = request->command_id;
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
|
||||||
|
|
||||||
const char* path = request->content.storage_timestamp_request.path;
|
const char* path = request->content.storage_timestamp_request.path;
|
||||||
uint32_t timestamp = 0;
|
uint32_t timestamp = 0;
|
||||||
FS_Error error = storage_common_timestamp(fs_api, path, ×tamp);
|
FS_Error error = storage_common_timestamp(rpc_storage->api, path, ×tamp);
|
||||||
|
|
||||||
response->command_status = rpc_system_storage_get_error(error);
|
response->command_status = rpc_system_storage_get_error(error);
|
||||||
response->which_content = PB_Main_empty_tag;
|
response->which_content = PB_Main_empty_tag;
|
||||||
@ -170,7 +165,6 @@ static void rpc_system_storage_timestamp_process(const PB_Main* request, void* c
|
|||||||
|
|
||||||
rpc_send_and_release(session, response);
|
rpc_send_and_release(session, response);
|
||||||
free(response);
|
free(response);
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_storage_stat_process(const PB_Main* request, void* context) {
|
static void rpc_system_storage_stat_process(const PB_Main* request, void* context) {
|
||||||
@ -189,11 +183,9 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex
|
|||||||
PB_Main* response = malloc(sizeof(PB_Main));
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
response->command_id = request->command_id;
|
response->command_id = request->command_id;
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
|
||||||
|
|
||||||
const char* path = request->content.storage_stat_request.path;
|
const char* path = request->content.storage_stat_request.path;
|
||||||
FileInfo fileinfo;
|
FileInfo fileinfo;
|
||||||
FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
|
FS_Error error = storage_common_stat(rpc_storage->api, path, &fileinfo);
|
||||||
|
|
||||||
response->command_status = rpc_system_storage_get_error(error);
|
response->command_status = rpc_system_storage_get_error(error);
|
||||||
response->which_content = PB_Main_empty_tag;
|
response->which_content = PB_Main_empty_tag;
|
||||||
@ -209,12 +201,12 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex
|
|||||||
|
|
||||||
rpc_send_and_release(session, response);
|
rpc_send_and_release(session, response);
|
||||||
free(response);
|
free(response);
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_storage_list_root(const PB_Main* request, void* context) {
|
static void rpc_system_storage_list_root(const PB_Main* request, void* context) {
|
||||||
furi_assert(request);
|
furi_assert(request);
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
|
|
||||||
RpcStorageSystem* rpc_storage = context;
|
RpcStorageSystem* rpc_storage = context;
|
||||||
RpcSession* session = rpc_storage->session;
|
RpcSession* session = rpc_storage->session;
|
||||||
furi_assert(session);
|
furi_assert(session);
|
||||||
@ -279,8 +271,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
File* dir = storage_file_alloc(rpc_storage->api);
|
||||||
File* dir = storage_file_alloc(fs_api);
|
|
||||||
|
|
||||||
PB_Main response = {
|
PB_Main response = {
|
||||||
.command_id = request->command_id,
|
.command_id = request->command_id,
|
||||||
@ -293,7 +284,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
|
|||||||
bool include_md5 = list_request->include_md5;
|
bool include_md5 = list_request->include_md5;
|
||||||
FuriString* md5 = furi_string_alloc();
|
FuriString* md5 = furi_string_alloc();
|
||||||
FuriString* md5_path = furi_string_alloc();
|
FuriString* md5_path = furi_string_alloc();
|
||||||
File* file = storage_file_alloc(fs_api);
|
File* file = storage_file_alloc(rpc_storage->api);
|
||||||
|
|
||||||
bool finish = false;
|
bool finish = false;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -350,8 +341,6 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
|
|||||||
storage_dir_close(dir);
|
storage_dir_close(dir);
|
||||||
storage_file_free(dir);
|
storage_file_free(dir);
|
||||||
storage_file_free(file);
|
storage_file_free(file);
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_storage_read_process(const PB_Main* request, void* context) {
|
static void rpc_system_storage_read_process(const PB_Main* request, void* context) {
|
||||||
@ -370,8 +359,7 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
|
|||||||
/* use same message memory to send response */
|
/* use same message memory to send response */
|
||||||
PB_Main* response = malloc(sizeof(PB_Main));
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
const char* path = request->content.storage_read_request.path;
|
const char* path = request->content.storage_read_request.path;
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
File* file = storage_file_alloc(rpc_storage->api);
|
||||||
File* file = storage_file_alloc(fs_api);
|
|
||||||
bool fs_operation_success = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING);
|
bool fs_operation_success = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||||
|
|
||||||
if(fs_operation_success) {
|
if(fs_operation_success) {
|
||||||
@ -420,8 +408,6 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
|
|||||||
free(response);
|
free(response);
|
||||||
storage_file_close(file);
|
storage_file_close(file);
|
||||||
storage_file_free(file);
|
storage_file_free(file);
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_storage_write_process(const PB_Main* request, void* context) {
|
static void rpc_system_storage_write_process(const PB_Main* request, void* context) {
|
||||||
@ -451,7 +437,6 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(rpc_storage->state != RpcStorageStateWriting) {
|
if(rpc_storage->state != RpcStorageStateWriting) {
|
||||||
rpc_storage->api = furi_record_open(RECORD_STORAGE);
|
|
||||||
rpc_storage->file = storage_file_alloc(rpc_storage->api);
|
rpc_storage->file = storage_file_alloc(rpc_storage->api);
|
||||||
rpc_storage->current_command_id = request->command_id;
|
rpc_storage->current_command_id = request->command_id;
|
||||||
rpc_storage->state = RpcStorageStateWriting;
|
rpc_storage->state = RpcStorageStateWriting;
|
||||||
@ -492,14 +477,15 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) {
|
static bool rpc_system_storage_is_dir_is_empty(Storage* storage, const char* path) {
|
||||||
furi_assert(fs_api);
|
furi_assert(storage);
|
||||||
furi_assert(path);
|
furi_assert(path);
|
||||||
|
|
||||||
FileInfo fileinfo;
|
FileInfo fileinfo;
|
||||||
bool is_dir_is_empty = true;
|
bool is_dir_is_empty = true;
|
||||||
FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
|
FS_Error error = storage_common_stat(storage, path, &fileinfo);
|
||||||
if((error == FSE_OK) && file_info_is_dir(&fileinfo)) {
|
if((error == FSE_OK) && file_info_is_dir(&fileinfo)) {
|
||||||
File* dir = storage_file_alloc(fs_api);
|
File* dir = storage_file_alloc(storage);
|
||||||
if(storage_dir_open(dir, path)) {
|
if(storage_dir_open(dir, path)) {
|
||||||
char* name = malloc(MAX_NAME_LENGTH);
|
char* name = malloc(MAX_NAME_LENGTH);
|
||||||
while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
|
while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
|
||||||
@ -531,18 +517,17 @@ static void rpc_system_storage_delete_process(const PB_Main* request, void* cont
|
|||||||
PB_CommandStatus status = PB_CommandStatus_ERROR;
|
PB_CommandStatus status = PB_CommandStatus_ERROR;
|
||||||
rpc_system_storage_reset_state(rpc_storage, session, true);
|
rpc_system_storage_reset_state(rpc_storage, session, true);
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
|
||||||
|
|
||||||
char* path = request->content.storage_delete_request.path;
|
char* path = request->content.storage_delete_request.path;
|
||||||
if(!path) {
|
if(!path) {
|
||||||
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
|
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
|
||||||
} else {
|
} else {
|
||||||
FS_Error error_remove = storage_common_remove(fs_api, path);
|
FS_Error error_remove = storage_common_remove(rpc_storage->api, path);
|
||||||
// FSE_DENIED is for empty directory, but not only for this
|
// FSE_DENIED is for empty directory, but not only for this
|
||||||
// that's why we have to check it
|
// that's why we have to check it
|
||||||
if((error_remove == FSE_DENIED) && !rpc_system_storage_is_dir_is_empty(fs_api, path)) {
|
if((error_remove == FSE_DENIED) &&
|
||||||
|
!rpc_system_storage_is_dir_is_empty(rpc_storage->api, path)) {
|
||||||
if(request->content.storage_delete_request.recursive) {
|
if(request->content.storage_delete_request.recursive) {
|
||||||
bool deleted = storage_simply_remove_recursive(fs_api, path);
|
bool deleted = storage_simply_remove_recursive(rpc_storage->api, path);
|
||||||
status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR;
|
status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR;
|
||||||
} else {
|
} else {
|
||||||
status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY;
|
status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY;
|
||||||
@ -554,7 +539,6 @@ static void rpc_system_storage_delete_process(const PB_Main* request, void* cont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
rpc_send_and_release_empty(session, request->command_id, status);
|
rpc_send_and_release_empty(session, request->command_id, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,11 +556,10 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte
|
|||||||
PB_CommandStatus status;
|
PB_CommandStatus status;
|
||||||
rpc_system_storage_reset_state(rpc_storage, session, true);
|
rpc_system_storage_reset_state(rpc_storage, session, true);
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
|
||||||
char* path = request->content.storage_mkdir_request.path;
|
char* path = request->content.storage_mkdir_request.path;
|
||||||
if(path) {
|
if(path) {
|
||||||
if(path_contains_only_ascii(path)) {
|
if(path_contains_only_ascii(path)) {
|
||||||
FS_Error error = storage_common_mkdir(fs_api, path);
|
FS_Error error = storage_common_mkdir(rpc_storage->api, path);
|
||||||
status = rpc_system_storage_get_error(error);
|
status = rpc_system_storage_get_error(error);
|
||||||
} else {
|
} else {
|
||||||
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
|
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
|
||||||
@ -584,7 +567,6 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte
|
|||||||
} else {
|
} else {
|
||||||
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
|
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
|
||||||
}
|
}
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
rpc_send_and_release_empty(session, request->command_id, status);
|
rpc_send_and_release_empty(session, request->command_id, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,8 +590,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
File* file = storage_file_alloc(rpc_storage->api);
|
||||||
File* file = storage_file_alloc(fs_api);
|
|
||||||
FuriString* md5 = furi_string_alloc();
|
FuriString* md5 = furi_string_alloc();
|
||||||
FS_Error file_error;
|
FS_Error file_error;
|
||||||
|
|
||||||
@ -633,8 +614,6 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont
|
|||||||
|
|
||||||
furi_string_free(md5);
|
furi_string_free(md5);
|
||||||
storage_file_free(file);
|
storage_file_free(file);
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_storage_rename_process(const PB_Main* request, void* context) {
|
static void rpc_system_storage_rename_process(const PB_Main* request, void* context) {
|
||||||
@ -651,11 +630,9 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont
|
|||||||
PB_CommandStatus status;
|
PB_CommandStatus status;
|
||||||
rpc_system_storage_reset_state(rpc_storage, session, true);
|
rpc_system_storage_reset_state(rpc_storage, session, true);
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
|
||||||
|
|
||||||
if(path_contains_only_ascii(request->content.storage_rename_request.new_path)) {
|
if(path_contains_only_ascii(request->content.storage_rename_request.new_path)) {
|
||||||
FS_Error error = storage_common_rename(
|
FS_Error error = storage_common_rename(
|
||||||
fs_api,
|
rpc_storage->api,
|
||||||
request->content.storage_rename_request.old_path,
|
request->content.storage_rename_request.old_path,
|
||||||
request->content.storage_rename_request.new_path);
|
request->content.storage_rename_request.new_path);
|
||||||
status = rpc_system_storage_get_error(error);
|
status = rpc_system_storage_get_error(error);
|
||||||
@ -663,7 +640,6 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont
|
|||||||
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
|
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
rpc_send_and_release_empty(session, request->command_id, status);
|
rpc_send_and_release_empty(session, request->command_id, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,12 +654,10 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi
|
|||||||
RpcSession* session = rpc_storage->session;
|
RpcSession* session = rpc_storage->session;
|
||||||
furi_assert(session);
|
furi_assert(session);
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
rpc_system_storage_reset_state(rpc_storage, session, true);
|
||||||
|
|
||||||
bool backup_ok =
|
bool backup_ok = lfs_backup_create(
|
||||||
lfs_backup_create(fs_api, request->content.storage_backup_create_request.archive_path);
|
rpc_storage->api, request->content.storage_backup_create_request.archive_path);
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
|
|
||||||
rpc_send_and_release_empty(
|
rpc_send_and_release_empty(
|
||||||
session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR);
|
session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR);
|
||||||
@ -700,17 +674,58 @@ static void rpc_system_storage_backup_restore_process(const PB_Main* request, vo
|
|||||||
RpcSession* session = rpc_storage->session;
|
RpcSession* session = rpc_storage->session;
|
||||||
furi_assert(session);
|
furi_assert(session);
|
||||||
|
|
||||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
rpc_system_storage_reset_state(rpc_storage, session, true);
|
||||||
|
|
||||||
bool backup_ok =
|
bool backup_ok = lfs_backup_unpack(
|
||||||
lfs_backup_unpack(fs_api, request->content.storage_backup_restore_request.archive_path);
|
rpc_storage->api, request->content.storage_backup_restore_request.archive_path);
|
||||||
|
|
||||||
furi_record_close(RECORD_STORAGE);
|
|
||||||
|
|
||||||
rpc_send_and_release_empty(
|
rpc_send_and_release_empty(
|
||||||
session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR);
|
session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rpc_system_storage_tar_extract_process(const PB_Main* request, void* context) {
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(request->which_content == PB_Main_storage_tar_extract_request_tag);
|
||||||
|
furi_assert(context);
|
||||||
|
|
||||||
|
FURI_LOG_D(TAG, "TarExtract");
|
||||||
|
|
||||||
|
RpcStorageSystem* rpc_storage = context;
|
||||||
|
RpcSession* session = rpc_storage->session;
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
PB_CommandStatus status;
|
||||||
|
rpc_system_storage_reset_state(rpc_storage, session, true);
|
||||||
|
|
||||||
|
TarArchive* archive = tar_archive_alloc(rpc_storage->api);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(!path_contains_only_ascii(request->content.storage_tar_extract_request.out_path)) {
|
||||||
|
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!tar_archive_open(
|
||||||
|
archive,
|
||||||
|
request->content.storage_tar_extract_request.tar_path,
|
||||||
|
TAR_OPEN_MODE_READ)) {
|
||||||
|
status = PB_CommandStatus_ERROR_STORAGE_INVALID_PARAMETER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!tar_archive_unpack_to(
|
||||||
|
archive, request->content.storage_tar_extract_request.out_path, NULL)) {
|
||||||
|
status = PB_CommandStatus_ERROR_STORAGE_INTERNAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = PB_CommandStatus_OK;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
tar_archive_free(archive);
|
||||||
|
rpc_send_and_release_empty(session, request->command_id, status);
|
||||||
|
}
|
||||||
|
|
||||||
void* rpc_system_storage_alloc(RpcSession* session) {
|
void* rpc_system_storage_alloc(RpcSession* session) {
|
||||||
furi_assert(session);
|
furi_assert(session);
|
||||||
|
|
||||||
@ -761,6 +776,9 @@ void* rpc_system_storage_alloc(RpcSession* session) {
|
|||||||
rpc_handler.message_handler = rpc_system_storage_backup_restore_process;
|
rpc_handler.message_handler = rpc_system_storage_backup_restore_process;
|
||||||
rpc_add_handler(session, PB_Main_storage_backup_restore_request_tag, &rpc_handler);
|
rpc_add_handler(session, PB_Main_storage_backup_restore_request_tag, &rpc_handler);
|
||||||
|
|
||||||
|
rpc_handler.message_handler = rpc_system_storage_tar_extract_process;
|
||||||
|
rpc_add_handler(session, PB_Main_storage_tar_extract_request_tag, &rpc_handler);
|
||||||
|
|
||||||
return rpc_storage;
|
return rpc_storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,5 +789,8 @@ void rpc_system_storage_free(void* context) {
|
|||||||
furi_assert(session);
|
furi_assert(session);
|
||||||
|
|
||||||
rpc_system_storage_reset_state(rpc_storage, session, false);
|
rpc_system_storage_reset_state(rpc_storage, session, false);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
rpc_storage->api = NULL;
|
||||||
free(rpc_storage);
|
free(rpc_storage);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 1956b83bba99313ee8d8386e5d35d0549341ca26
|
Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a
|
@ -45,11 +45,13 @@ env.Append(
|
|||||||
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
|
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
|
||||||
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
|
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
|
||||||
File("protocols/mf_classic/mf_classic_listener.h"),
|
File("protocols/mf_classic/mf_classic_listener.h"),
|
||||||
|
File("protocols/felica/felica_listener.h"),
|
||||||
# Sync API
|
# Sync API
|
||||||
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
|
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
|
||||||
File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"),
|
File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"),
|
||||||
File("protocols/mf_classic/mf_classic_poller_sync.h"),
|
File("protocols/mf_classic/mf_classic_poller_sync.h"),
|
||||||
File("protocols/st25tb/st25tb_poller_sync.h"),
|
File("protocols/st25tb/st25tb_poller_sync.h"),
|
||||||
|
File("protocols/felica/felica_poller_sync.h"),
|
||||||
# Misc
|
# Misc
|
||||||
File("helpers/nfc_util.h"),
|
File("helpers/nfc_util.h"),
|
||||||
File("helpers/iso14443_crc.h"),
|
File("helpers/iso14443_crc.h"),
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#define FELICA_CRC_INIT (0x0000U)
|
#define FELICA_CRC_INIT (0x0000U)
|
||||||
|
|
||||||
uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
||||||
|
furi_check(data);
|
||||||
|
|
||||||
uint16_t crc = FELICA_CRC_INIT;
|
uint16_t crc = FELICA_CRC_INIT;
|
||||||
|
|
||||||
for(size_t i = 0; i < length; i++) {
|
for(size_t i = 0; i < length; i++) {
|
||||||
@ -24,6 +26,7 @@ uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void felica_crc_append(BitBuffer* buf) {
|
void felica_crc_append(BitBuffer* buf) {
|
||||||
|
furi_check(buf);
|
||||||
const uint8_t* data = bit_buffer_get_data(buf);
|
const uint8_t* data = bit_buffer_get_data(buf);
|
||||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||||
|
|
||||||
@ -32,6 +35,7 @@ void felica_crc_append(BitBuffer* buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool felica_crc_check(const BitBuffer* buf) {
|
bool felica_crc_check(const BitBuffer* buf) {
|
||||||
|
furi_check(buf);
|
||||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||||
if(data_size <= FELICA_CRC_SIZE) return false;
|
if(data_size <= FELICA_CRC_SIZE) return false;
|
||||||
|
|
||||||
@ -45,6 +49,7 @@ bool felica_crc_check(const BitBuffer* buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void felica_crc_trim(BitBuffer* buf) {
|
void felica_crc_trim(BitBuffer* buf) {
|
||||||
|
furi_check(buf);
|
||||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||||
furi_assert(data_size > FELICA_CRC_SIZE);
|
furi_assert(data_size > FELICA_CRC_SIZE);
|
||||||
|
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
#include <lib/nfc/nfc.h>
|
#include <lib/nfc/nfc.h>
|
||||||
#include <lib/nfc/helpers/iso14443_crc.h>
|
#include <lib/nfc/helpers/iso14443_crc.h>
|
||||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||||
|
#include <lib/nfc/protocols/felica/felica.h>
|
||||||
|
#include <lib/nfc/helpers/felica_crc.h>
|
||||||
|
#include <lib/nfc/protocols/felica/felica_poller_sync.h>
|
||||||
|
|
||||||
#include <furi/furi.h>
|
#include <furi/furi.h>
|
||||||
|
|
||||||
@ -50,11 +53,31 @@ typedef struct {
|
|||||||
Iso14443_3aSelResp sel_resp[2];
|
Iso14443_3aSelResp sel_resp[2];
|
||||||
} Iso14443_3aColResData;
|
} Iso14443_3aColResData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t length;
|
||||||
|
uint8_t polling_cmd;
|
||||||
|
uint16_t system_code;
|
||||||
|
uint8_t request_code;
|
||||||
|
uint8_t time_slot;
|
||||||
|
} FelicaPollingRequest;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t code;
|
||||||
|
FelicaIDm idm;
|
||||||
|
FelicaPMm pmm;
|
||||||
|
} FelicaSensfResData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t system_code;
|
||||||
|
FelicaSensfResData sens_res;
|
||||||
|
} FelicaPTMemory;
|
||||||
|
|
||||||
struct Nfc {
|
struct Nfc {
|
||||||
NfcState state;
|
NfcState state;
|
||||||
|
|
||||||
Iso14443_3aColResStatus col_res_status;
|
Iso14443_3aColResStatus col_res_status;
|
||||||
Iso14443_3aColResData col_res_data;
|
Iso14443_3aColResData col_res_data;
|
||||||
|
FelicaPTMemory pt_memory;
|
||||||
bool software_col_res_required;
|
bool software_col_res_required;
|
||||||
|
|
||||||
NfcEventCallback callback;
|
NfcEventCallback callback;
|
||||||
@ -243,6 +266,21 @@ static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, ui
|
|||||||
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||||
instance->callback(event, instance->context);
|
instance->callback(event, instance->context);
|
||||||
|
|
||||||
|
processed = true;
|
||||||
|
}
|
||||||
|
} else if(rx_bits == 8 * 8) {
|
||||||
|
FelicaPollingRequest* request = (FelicaPollingRequest*)rx_data;
|
||||||
|
if(request->system_code == instance->pt_memory.system_code) {
|
||||||
|
uint8_t response_size = sizeof(FelicaSensfResData) + 1;
|
||||||
|
bit_buffer_reset(tx_buffer);
|
||||||
|
bit_buffer_append_byte(tx_buffer, response_size);
|
||||||
|
bit_buffer_append_bytes(
|
||||||
|
tx_buffer, (uint8_t*)&instance->pt_memory.sens_res, sizeof(FelicaSensfResData));
|
||||||
|
felica_crc_append(tx_buffer);
|
||||||
|
nfc_listener_tx(instance, tx_buffer);
|
||||||
|
instance->col_res_status = Iso14443_3aColResStatusDone;
|
||||||
|
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||||
|
instance->callback(event, instance->context);
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,6 +508,12 @@ NfcError nfc_felica_listener_set_sensf_res_data(
|
|||||||
furi_assert(idm_len == 8);
|
furi_assert(idm_len == 8);
|
||||||
furi_assert(pmm_len == 8);
|
furi_assert(pmm_len == 8);
|
||||||
|
|
||||||
|
instance->pt_memory.system_code = 0xFFFF;
|
||||||
|
instance->pt_memory.sens_res.code = 0x01;
|
||||||
|
instance->software_col_res_required = true;
|
||||||
|
memcpy(instance->pt_memory.sens_res.idm.data, idm, idm_len);
|
||||||
|
memcpy(instance->pt_memory.sens_res.pmm.data, pmm, pmm_len);
|
||||||
|
|
||||||
return NfcErrorNone;
|
return NfcErrorNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,15 +298,33 @@ bool felica_check_mac(
|
|||||||
furi_check(blocks);
|
furi_check(blocks);
|
||||||
furi_check(data);
|
furi_check(data);
|
||||||
|
|
||||||
uint8_t first_block[8];
|
|
||||||
uint8_t mac[8];
|
uint8_t mac[8];
|
||||||
felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block);
|
felica_calculate_mac_read(ctx, session_key, rc, blocks, block_count, data, mac);
|
||||||
|
|
||||||
|
uint8_t mac_offset = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
||||||
|
uint8_t* mac_ptr = data + mac_offset;
|
||||||
|
return !memcmp(mac, mac_ptr, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void felica_calculate_mac_read(
|
||||||
|
mbedtls_des3_context* ctx,
|
||||||
|
const uint8_t* session_key,
|
||||||
|
const uint8_t* rc,
|
||||||
|
const uint8_t* blocks,
|
||||||
|
const uint8_t block_count,
|
||||||
|
const uint8_t* data,
|
||||||
|
uint8_t* mac) {
|
||||||
|
furi_check(ctx);
|
||||||
|
furi_check(session_key);
|
||||||
|
furi_check(rc);
|
||||||
|
furi_check(blocks);
|
||||||
|
furi_check(data);
|
||||||
|
furi_check(mac);
|
||||||
|
|
||||||
|
uint8_t first_block[8];
|
||||||
|
felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block);
|
||||||
uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
||||||
felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac);
|
felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac);
|
||||||
|
|
||||||
uint8_t* mac_ptr = data + data_size_without_mac;
|
|
||||||
return !memcmp(mac, mac_ptr, 8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void felica_calculate_mac_write(
|
void felica_calculate_mac_write(
|
||||||
|
@ -12,7 +12,13 @@ extern "C" {
|
|||||||
#define FELICA_PMM_SIZE (8U)
|
#define FELICA_PMM_SIZE (8U)
|
||||||
#define FELICA_DATA_BLOCK_SIZE (16U)
|
#define FELICA_DATA_BLOCK_SIZE (16U)
|
||||||
|
|
||||||
#define FELICA_BLOCKS_TOTAL_COUNT (27U)
|
#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U)
|
||||||
|
#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U)
|
||||||
|
|
||||||
|
#define FELICA_SERVICE_RW_ACCESS (0x0009U)
|
||||||
|
#define FELICA_SERVICE_RO_ACCESS (0x000BU)
|
||||||
|
|
||||||
|
#define FELICA_BLOCKS_TOTAL_COUNT (28U)
|
||||||
#define FELICA_BLOCK_INDEX_REG (0x0EU)
|
#define FELICA_BLOCK_INDEX_REG (0x0EU)
|
||||||
#define FELICA_BLOCK_INDEX_RC (0x80U)
|
#define FELICA_BLOCK_INDEX_RC (0x80U)
|
||||||
#define FELICA_BLOCK_INDEX_MAC (0x81U)
|
#define FELICA_BLOCK_INDEX_MAC (0x81U)
|
||||||
@ -54,6 +60,10 @@ typedef enum {
|
|||||||
FelicaErrorTimeout,
|
FelicaErrorTimeout,
|
||||||
} FelicaError;
|
} FelicaError;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||||
|
} FelicaBlockData;
|
||||||
|
|
||||||
/** @brief Separate type for card key block. Used in authentication process */
|
/** @brief Separate type for card key block. Used in authentication process */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||||
@ -76,6 +86,22 @@ typedef struct {
|
|||||||
FelicaAuthenticationStatus auth_status; /**< Authentication status*/
|
FelicaAuthenticationStatus auth_status; /**< Authentication status*/
|
||||||
} FelicaAuthenticationContext;
|
} FelicaAuthenticationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stucture for holding Felica session key which is calculated from rc and ck.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||||
|
} FelicaSessionKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure used to hold authentication related fields.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */
|
||||||
|
FelicaSessionKey session_key; /**< Calculated session key. */
|
||||||
|
FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */
|
||||||
|
} FelicaAuthentication;
|
||||||
|
|
||||||
/** @brief Felica ID block */
|
/** @brief Felica ID block */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t data[FELICA_IDM_SIZE];
|
uint8_t data[FELICA_IDM_SIZE];
|
||||||
@ -128,6 +154,51 @@ typedef struct {
|
|||||||
FelicaFSUnion data;
|
FelicaFSUnion data;
|
||||||
} FelicaData;
|
} FelicaData;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
uint8_t code;
|
||||||
|
FelicaIDm idm;
|
||||||
|
uint8_t service_num;
|
||||||
|
uint16_t service_code;
|
||||||
|
uint8_t block_count;
|
||||||
|
} FelicaCommandHeader;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t length;
|
||||||
|
uint8_t response_code;
|
||||||
|
FelicaIDm idm;
|
||||||
|
uint8_t SF1;
|
||||||
|
uint8_t SF2;
|
||||||
|
} FelicaCommandResponseHeader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t service_code : 4;
|
||||||
|
uint8_t access_mode : 3;
|
||||||
|
uint8_t length : 1;
|
||||||
|
uint8_t block_number;
|
||||||
|
} FelicaBlockListElement;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t length;
|
||||||
|
uint8_t response_code;
|
||||||
|
FelicaIDm idm;
|
||||||
|
uint8_t SF1;
|
||||||
|
uint8_t SF2;
|
||||||
|
uint8_t block_count;
|
||||||
|
uint8_t data[];
|
||||||
|
} FelicaPollerReadCommandResponse;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FelicaCommandResponseHeader header;
|
||||||
|
uint8_t block_count;
|
||||||
|
uint8_t data[];
|
||||||
|
} FelicaListenerReadCommandResponse;
|
||||||
|
|
||||||
|
typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse;
|
||||||
|
|
||||||
|
typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse;
|
||||||
|
|
||||||
extern const NfcDeviceBase nfc_device_felica;
|
extern const NfcDeviceBase nfc_device_felica;
|
||||||
|
|
||||||
FelicaData* felica_alloc(void);
|
FelicaData* felica_alloc(void);
|
||||||
@ -168,6 +239,15 @@ bool felica_check_mac(
|
|||||||
const uint8_t block_count,
|
const uint8_t block_count,
|
||||||
uint8_t* data);
|
uint8_t* data);
|
||||||
|
|
||||||
|
void felica_calculate_mac_read(
|
||||||
|
mbedtls_des3_context* ctx,
|
||||||
|
const uint8_t* session_key,
|
||||||
|
const uint8_t* rc,
|
||||||
|
const uint8_t* blocks,
|
||||||
|
const uint8_t block_count,
|
||||||
|
const uint8_t* data,
|
||||||
|
uint8_t* mac);
|
||||||
|
|
||||||
void felica_calculate_mac_write(
|
void felica_calculate_mac_write(
|
||||||
mbedtls_des3_context* ctx,
|
mbedtls_des3_context* ctx,
|
||||||
const uint8_t* session_key,
|
const uint8_t* session_key,
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
#include "felica_listener_i.h"
|
#include "felica_listener_i.h"
|
||||||
|
|
||||||
#include "nfc/protocols/nfc_listener_base.h"
|
#include "nfc/protocols/nfc_listener_base.h"
|
||||||
|
#include <nfc/helpers/felica_crc.h>
|
||||||
|
#include <furi_hal_nfc.h>
|
||||||
|
|
||||||
#define FELICA_LISTENER_MAX_BUFFER_SIZE (64)
|
#define FELICA_LISTENER_MAX_BUFFER_SIZE (128)
|
||||||
#define TAG "Felica"
|
#define FELICA_LISTENER_RESPONSE_CODE_READ (0x07)
|
||||||
|
#define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09)
|
||||||
|
|
||||||
|
#define TAG "FelicaListener"
|
||||||
|
|
||||||
FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
||||||
furi_assert(nfc);
|
furi_assert(nfc);
|
||||||
@ -15,8 +20,11 @@ FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
|||||||
instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
||||||
instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
||||||
|
|
||||||
|
mbedtls_des3_init(&instance->auth.des_context);
|
||||||
nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC);
|
nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC);
|
||||||
|
|
||||||
|
memcpy(instance->mc_shadow.data, instance->data->data.fs.mc.data, FELICA_DATA_BLOCK_SIZE);
|
||||||
|
instance->data->data.fs.state.data[0] = 0;
|
||||||
nfc_config(instance->nfc, NfcModeListener, NfcTechFelica);
|
nfc_config(instance->nfc, NfcModeListener, NfcTechFelica);
|
||||||
nfc_felica_listener_set_sensf_res_data(
|
nfc_felica_listener_set_sensf_res_data(
|
||||||
nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm));
|
nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm));
|
||||||
@ -49,6 +57,99 @@ const FelicaData* felica_listener_get_data(const FelicaListener* instance) {
|
|||||||
return instance->data;
|
return instance->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FelicaError felica_listener_command_handler_read(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerGenericRequest* const generic_request) {
|
||||||
|
const FelicaListenerReadRequest* request = (FelicaListenerReadRequest*)generic_request;
|
||||||
|
FURI_LOG_D(TAG, "Read cmd");
|
||||||
|
|
||||||
|
FelicaListenerReadCommandResponse* resp = malloc(
|
||||||
|
sizeof(FelicaCommandResponseHeader) + 1 +
|
||||||
|
FELICA_LISTENER_READ_BLOCK_COUNT_MAX * FELICA_DATA_BLOCK_SIZE);
|
||||||
|
|
||||||
|
resp->header.response_code = FELICA_LISTENER_RESPONSE_CODE_READ;
|
||||||
|
resp->header.idm = request->base.header.idm;
|
||||||
|
resp->header.length = sizeof(FelicaCommandResponseHeader);
|
||||||
|
|
||||||
|
if(felica_listener_validate_read_request_and_set_sf(instance, request, &resp->header)) {
|
||||||
|
resp->block_count = request->base.header.block_count;
|
||||||
|
resp->header.length++;
|
||||||
|
} else {
|
||||||
|
resp->block_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->mac_calc_start = 0;
|
||||||
|
memset(instance->requested_blocks, 0, sizeof(instance->requested_blocks));
|
||||||
|
const FelicaBlockListElement* item =
|
||||||
|
felica_listener_block_list_item_get_first(instance, request);
|
||||||
|
for(uint8_t i = 0; i < resp->block_count; i++) {
|
||||||
|
instance->requested_blocks[i] = item->block_number;
|
||||||
|
FelicaCommanReadBlockHandler handler =
|
||||||
|
felica_listener_get_read_block_handler(item->block_number);
|
||||||
|
|
||||||
|
handler(instance, item->block_number, i, resp);
|
||||||
|
|
||||||
|
item = felica_listener_block_list_item_get_next(instance, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length);
|
||||||
|
free(resp);
|
||||||
|
|
||||||
|
return felica_listener_frame_exchange(instance, instance->tx_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FelicaError felica_listener_command_handler_write(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerGenericRequest* const generic_request) {
|
||||||
|
FURI_LOG_D(TAG, "Write cmd");
|
||||||
|
|
||||||
|
const FelicaListenerWriteRequest* request = (FelicaListenerWriteRequest*)generic_request;
|
||||||
|
const FelicaListenerWriteBlockData* data_ptr =
|
||||||
|
felica_listener_get_write_request_data_pointer(instance, generic_request);
|
||||||
|
|
||||||
|
FelicaListenerWriteCommandResponse* resp = malloc(sizeof(FelicaListenerWriteCommandResponse));
|
||||||
|
|
||||||
|
resp->response_code = FELICA_LISTENER_RESPONSE_CODE_WRITE;
|
||||||
|
resp->idm = request->base.header.idm;
|
||||||
|
resp->length = sizeof(FelicaListenerWriteCommandResponse);
|
||||||
|
|
||||||
|
if(felica_listener_validate_write_request_and_set_sf(instance, request, data_ptr, resp)) {
|
||||||
|
const FelicaBlockListElement* item =
|
||||||
|
felica_listener_block_list_item_get_first(instance, request);
|
||||||
|
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||||
|
FelicaCommandWriteBlockHandler handler =
|
||||||
|
felica_listener_get_write_block_handler(item->block_number);
|
||||||
|
|
||||||
|
handler(instance, item->block_number, &data_ptr->blocks[i]);
|
||||||
|
|
||||||
|
item = felica_listener_block_list_item_get_next(instance, item);
|
||||||
|
}
|
||||||
|
felica_wcnt_increment(instance->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_buffer_reset(instance->tx_buffer);
|
||||||
|
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->length);
|
||||||
|
free(resp);
|
||||||
|
|
||||||
|
return felica_listener_frame_exchange(instance, instance->tx_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FelicaError felica_listener_process_request(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerGenericRequest* generic_request) {
|
||||||
|
const uint8_t cmd_code = generic_request->header.code;
|
||||||
|
switch(cmd_code) {
|
||||||
|
case FELICA_CMD_READ_WITHOUT_ENCRYPTION:
|
||||||
|
return felica_listener_command_handler_read(instance, generic_request);
|
||||||
|
case FELICA_CMD_WRITE_WITHOUT_ENCRYPTION:
|
||||||
|
return felica_listener_command_handler_write(instance, generic_request);
|
||||||
|
default:
|
||||||
|
FURI_LOG_E(TAG, "FeliCa incorrect command");
|
||||||
|
return FelicaErrorNotPresent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||||
@ -58,14 +159,44 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
|||||||
NfcEvent* nfc_event = event.event_data;
|
NfcEvent* nfc_event = event.event_data;
|
||||||
NfcCommand command = NfcCommandContinue;
|
NfcCommand command = NfcCommandContinue;
|
||||||
|
|
||||||
if(nfc_event->type == NfcEventTypeListenerActivated) {
|
if(nfc_event->type == NfcEventTypeFieldOn) {
|
||||||
|
FURI_LOG_D(TAG, "Field On");
|
||||||
|
} else if(nfc_event->type == NfcEventTypeListenerActivated) {
|
||||||
instance->state = Felica_ListenerStateActivated;
|
instance->state = Felica_ListenerStateActivated;
|
||||||
FURI_LOG_D(TAG, "Activated");
|
FURI_LOG_D(TAG, "Activated");
|
||||||
} else if(nfc_event->type == NfcEventTypeFieldOff) {
|
} else if(nfc_event->type == NfcEventTypeFieldOff) {
|
||||||
instance->state = Felica_ListenerStateIdle;
|
instance->state = Felica_ListenerStateIdle;
|
||||||
FURI_LOG_D(TAG, "Field Off");
|
FURI_LOG_D(TAG, "Field Off");
|
||||||
|
felica_listener_reset(instance);
|
||||||
} else if(nfc_event->type == NfcEventTypeRxEnd) {
|
} else if(nfc_event->type == NfcEventTypeRxEnd) {
|
||||||
FURI_LOG_D(TAG, "Rx Done");
|
FURI_LOG_D(TAG, "Rx Done");
|
||||||
|
do {
|
||||||
|
if(!felica_crc_check(nfc_event->data.buffer)) {
|
||||||
|
FURI_LOG_E(TAG, "Wrong CRC");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FelicaListenerGenericRequest* request =
|
||||||
|
(FelicaListenerGenericRequest*)bit_buffer_get_data(nfc_event->data.buffer);
|
||||||
|
|
||||||
|
uint8_t size = bit_buffer_get_size_bytes(nfc_event->data.buffer) - 2;
|
||||||
|
if((request->length != size) ||
|
||||||
|
(!felica_listener_check_block_list_size(instance, request))) {
|
||||||
|
FURI_LOG_E(TAG, "Wrong request length");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!felica_listener_check_idm(instance, &request->header.idm)) {
|
||||||
|
FURI_LOG_E(TAG, "Wrong IDm");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FelicaError error = felica_listener_process_request(instance, request);
|
||||||
|
if(error != FelicaErrorNone) {
|
||||||
|
FURI_LOG_E(TAG, "Processing error: %2X", error);
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
bit_buffer_reset(nfc_event->data.buffer);
|
||||||
}
|
}
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
738
lib/nfc/protocols/felica/felica_listener_i.c
Normal file
738
lib/nfc/protocols/felica/felica_listener_i.c
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
#include "felica_listener_i.h"
|
||||||
|
|
||||||
|
#include <nfc/helpers/felica_crc.h>
|
||||||
|
|
||||||
|
#define FELICA_WCNT_MC2_FF_MAX_VALUE (0x00FFFFFFU)
|
||||||
|
#define FELICA_WCNT_MC2_00_MAX_VALUE (0x00FFFE00U)
|
||||||
|
#define FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE (0x00001027U)
|
||||||
|
#define FELICA_WCNT_MC2_00_WARNING_END_VALUE (0x00FFFDFFU)
|
||||||
|
|
||||||
|
#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U)
|
||||||
|
#define FELICA_MC_ALL_BYTE (2U)
|
||||||
|
#define FELICA_MC_SYS_OP (3U)
|
||||||
|
#define FELICA_MC_RF_PRM (4U)
|
||||||
|
#define FELICA_MC_CKCKV_W_MAC_A (5U)
|
||||||
|
#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U)
|
||||||
|
#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U)
|
||||||
|
#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U)
|
||||||
|
#define FELICA_MC_STATE_W_MAC_A (12U)
|
||||||
|
#define FELICA_MC_RESERVED_13 (13U)
|
||||||
|
#define FELICA_MC_RESERVED_14 (14U)
|
||||||
|
#define FELICA_MC_RESERVED_15 (15U)
|
||||||
|
|
||||||
|
#define FELICA_MC_BYTE_GET(data, byte) (data->data.fs.mc.data[byte])
|
||||||
|
#define FELICA_SYSTEM_BLOCK_RO_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0x00)
|
||||||
|
#define FELICA_SYSTEM_BLOCK_RW_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0xFF)
|
||||||
|
|
||||||
|
#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN ((uint8_t)sizeof(FelicaBlockListElement))
|
||||||
|
#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item) \
|
||||||
|
((item->length == 1) ? FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN : \
|
||||||
|
(FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN + 1))
|
||||||
|
|
||||||
|
static uint32_t felica_wcnt_get_max_value(const FelicaData* data) {
|
||||||
|
if(!FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && !FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) {
|
||||||
|
furi_crash("MC[2] reserved values are forbidden");
|
||||||
|
}
|
||||||
|
return (FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) ? FELICA_WCNT_MC2_FF_MAX_VALUE :
|
||||||
|
FELICA_WCNT_MC2_00_MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_wcnt_check_warning_boundary(const FelicaData* data) {
|
||||||
|
const uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||||
|
return (
|
||||||
|
FELICA_SYSTEM_BLOCK_RO_ACCESS(data) &&
|
||||||
|
((*wcnt_ptr > FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE) &&
|
||||||
|
(*wcnt_ptr < FELICA_WCNT_MC2_00_WARNING_END_VALUE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_wcnt_check_error_boundary(const FelicaData* data) {
|
||||||
|
const uint32_t wcnt_max = felica_wcnt_get_max_value(data);
|
||||||
|
const uint32_t* wcnt_ptr = (const uint32_t*)data->data.fs.wcnt.data;
|
||||||
|
return *wcnt_ptr != wcnt_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
void felica_wcnt_increment(FelicaData* data) {
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
const uint32_t wcnt_max = felica_wcnt_get_max_value(data);
|
||||||
|
uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||||
|
if(*wcnt_ptr < wcnt_max) {
|
||||||
|
*wcnt_ptr += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_wcnt_post_process(FelicaData* data) {
|
||||||
|
uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||||
|
|
||||||
|
if(FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && (*wcnt_ptr > FELICA_WCNT_MC2_00_MAX_VALUE)) {
|
||||||
|
*wcnt_ptr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
felica_listener_block_list_item_validate_block_number(const FelicaBlockListElement* item) {
|
||||||
|
bool valid = true;
|
||||||
|
if(item->length == 0) {
|
||||||
|
uint8_t D2_block_number = *(&item->block_number + 1);
|
||||||
|
valid = D2_block_number == 0;
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FelicaBlockListElement* felica_listener_block_list_iterate(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaBlockListElement* prev_item,
|
||||||
|
const uint8_t item_size) {
|
||||||
|
FelicaBlockListElement* next_item = NULL;
|
||||||
|
if(instance->request_size_buf >= FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN) {
|
||||||
|
next_item = (FelicaBlockListElement*)((uint8_t*)prev_item + item_size);
|
||||||
|
|
||||||
|
uint8_t next_item_size = FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(next_item);
|
||||||
|
|
||||||
|
if(instance->request_size_buf < next_item_size) {
|
||||||
|
next_item = NULL;
|
||||||
|
instance->request_size_buf = 0;
|
||||||
|
} else {
|
||||||
|
instance->request_size_buf -= next_item_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FelicaBlockListElement* felica_listener_block_list_item_get_first(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerRequest* request) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(request);
|
||||||
|
|
||||||
|
instance->request_size_buf = request->base.length - sizeof(FelicaListenerGenericRequest);
|
||||||
|
return felica_listener_block_list_iterate(instance, request->list, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FelicaBlockListElement* felica_listener_block_list_item_get_next(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaBlockListElement* prev_item) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(prev_item);
|
||||||
|
|
||||||
|
return felica_listener_block_list_iterate(
|
||||||
|
instance, prev_item, FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(prev_item));
|
||||||
|
}
|
||||||
|
|
||||||
|
const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer(
|
||||||
|
const FelicaListener* const instance,
|
||||||
|
const FelicaListenerGenericRequest* const generic_request) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(generic_request);
|
||||||
|
|
||||||
|
return (const FelicaListenerWriteBlockData*)((uint8_t*)generic_request +
|
||||||
|
sizeof(FelicaListenerGenericRequest) +
|
||||||
|
instance->block_list_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_listener_check_write_request_data_size(
|
||||||
|
const FelicaListener* const instance,
|
||||||
|
const FelicaListenerRequest* request,
|
||||||
|
const uint8_t fact_item_cnt) {
|
||||||
|
uint8_t possible_data_size = fact_item_cnt * FELICA_DATA_BLOCK_SIZE;
|
||||||
|
uint8_t fact_data_size =
|
||||||
|
request->base.length - sizeof(FelicaListenerGenericRequest) - instance->block_list_size;
|
||||||
|
return (possible_data_size <= fact_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_listener_test_block_list_size_bounds(const FelicaListenerGenericRequest* req) {
|
||||||
|
bool valid = false;
|
||||||
|
if(req->header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) {
|
||||||
|
valid =
|
||||||
|
(req->header.block_count >= FELICA_LISTENER_READ_BLOCK_COUNT_MIN &&
|
||||||
|
req->header.block_count <= FELICA_LISTENER_READ_BLOCK_COUNT_MAX);
|
||||||
|
} else if(req->header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||||
|
valid =
|
||||||
|
(req->header.block_count >= FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN &&
|
||||||
|
req->header.block_count <= FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t felica_listener_get_block_list_item_count_size(
|
||||||
|
FelicaListener* instance,
|
||||||
|
FelicaListenerRequest* request) {
|
||||||
|
uint8_t list_size = 0;
|
||||||
|
uint8_t item_cnt = 0;
|
||||||
|
|
||||||
|
uint8_t max_item_cnt = (request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) ?
|
||||||
|
FELICA_LISTENER_READ_BLOCK_COUNT_MAX :
|
||||||
|
FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX;
|
||||||
|
|
||||||
|
const FelicaBlockListElement* item =
|
||||||
|
felica_listener_block_list_item_get_first(instance, request);
|
||||||
|
|
||||||
|
while((item != NULL) && (item_cnt < max_item_cnt)) {
|
||||||
|
item_cnt++;
|
||||||
|
|
||||||
|
if(request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||||
|
if(instance->request_size_buf >= FELICA_DATA_BLOCK_SIZE) {
|
||||||
|
instance->request_size_buf -= FELICA_DATA_BLOCK_SIZE;
|
||||||
|
} else {
|
||||||
|
instance->request_size_buf = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list_size += FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item);
|
||||||
|
item = felica_listener_block_list_item_get_next(instance, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->block_list_size = list_size;
|
||||||
|
return item_cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool felica_listener_check_block_list_size(
|
||||||
|
FelicaListener* instance,
|
||||||
|
FelicaListenerGenericRequest* req) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(req);
|
||||||
|
|
||||||
|
FelicaListenerRequest* request = (FelicaListenerRequest*)req;
|
||||||
|
bool valid = true;
|
||||||
|
|
||||||
|
uint8_t item_cnt = felica_listener_get_block_list_item_count_size(instance, request);
|
||||||
|
valid = (item_cnt > 0);
|
||||||
|
|
||||||
|
if(felica_listener_test_block_list_size_bounds(req) &&
|
||||||
|
(item_cnt < request->base.header.block_count)) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(valid && request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||||
|
valid &= felica_listener_check_write_request_data_size(instance, request, item_cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(request_idm);
|
||||||
|
|
||||||
|
const FelicaIDm* idm = &instance->data->idm;
|
||||||
|
return memcmp(idm->data, request_idm->data, 8) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void felica_listener_reset(FelicaListener* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
instance->auth.context.auth_status.internal = false;
|
||||||
|
instance->auth.context.auth_status.external = false;
|
||||||
|
instance->data->data.fs.state.data[0] = 0;
|
||||||
|
instance->rc_written = false;
|
||||||
|
memset(instance->auth.session_key.data, 0, FELICA_DATA_BLOCK_SIZE);
|
||||||
|
|
||||||
|
memcpy(instance->data->data.fs.mc.data, instance->mc_shadow.data, FELICA_DATA_BLOCK_SIZE);
|
||||||
|
|
||||||
|
felica_wcnt_post_process(instance->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t felica_listener_get_block_index(uint8_t number) {
|
||||||
|
if(number >= FELICA_BLOCK_INDEX_RC && number < FELICA_BLOCK_INDEX_WCNT) {
|
||||||
|
return number - 0x80 + FELICA_BLOCK_INDEX_REG + 1;
|
||||||
|
} else if(number >= FELICA_BLOCK_INDEX_WCNT && number <= FELICA_BLOCK_INDEX_STATE) {
|
||||||
|
return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 1;
|
||||||
|
} else if(number == FELICA_BLOCK_INDEX_CRC_CHECK) {
|
||||||
|
return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_block_exists(uint8_t number) {
|
||||||
|
return !(
|
||||||
|
(number > FELICA_BLOCK_INDEX_REG && number < FELICA_BLOCK_INDEX_RC) ||
|
||||||
|
(number > FELICA_BLOCK_INDEX_MC && number < FELICA_BLOCK_INDEX_WCNT) ||
|
||||||
|
(number > FELICA_BLOCK_INDEX_STATE && number < FELICA_BLOCK_INDEX_CRC_CHECK) ||
|
||||||
|
(number > FELICA_BLOCK_INDEX_CRC_CHECK));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
felica_get_mc_bit(const FelicaListener* instance, uint8_t byte_index, uint8_t bit_number) {
|
||||||
|
uint8_t* mc = instance->data->data.fs.mc.data;
|
||||||
|
|
||||||
|
uint16_t flags = *((uint16_t*)&mc[byte_index]);
|
||||||
|
bool bit = (flags & (1 << bit_number)) != 0;
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_block_requires_auth(
|
||||||
|
const FelicaListener* instance,
|
||||||
|
uint8_t command,
|
||||||
|
uint8_t block_number) {
|
||||||
|
bool need_auth = false;
|
||||||
|
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||||
|
uint8_t mc_flag_index = command;
|
||||||
|
need_auth = felica_get_mc_bit(instance, mc_flag_index, block_number);
|
||||||
|
}
|
||||||
|
return need_auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_block_is_readonly(const FelicaListener* instance, uint8_t block_number) {
|
||||||
|
bool readonly = false;
|
||||||
|
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||||
|
readonly = !felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, block_number);
|
||||||
|
} else if(
|
||||||
|
((block_number == FELICA_BLOCK_INDEX_ID) || (block_number == FELICA_BLOCK_INDEX_SER_C)) ||
|
||||||
|
(block_number >= FELICA_BLOCK_INDEX_CKV && block_number <= FELICA_BLOCK_INDEX_CK)) {
|
||||||
|
const FelicaData* data = instance->data;
|
||||||
|
readonly = FELICA_SYSTEM_BLOCK_RO_ACCESS(data);
|
||||||
|
} else if(
|
||||||
|
(block_number == FELICA_BLOCK_INDEX_MAC) || (block_number == FELICA_BLOCK_INDEX_WCNT) ||
|
||||||
|
(block_number == FELICA_BLOCK_INDEX_D_ID) ||
|
||||||
|
(block_number == FELICA_BLOCK_INDEX_CRC_CHECK)) {
|
||||||
|
readonly = true;
|
||||||
|
}
|
||||||
|
return readonly;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_block_requires_mac(const FelicaListener* instance, uint8_t block_number) {
|
||||||
|
bool need_mac = false;
|
||||||
|
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||||
|
need_mac = felica_get_mc_bit(instance, FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11, block_number);
|
||||||
|
} else if(block_number == FELICA_BLOCK_INDEX_CK || block_number == FELICA_BLOCK_INDEX_CKV) {
|
||||||
|
need_mac = felica_get_mc_bit(instance, FELICA_MC_CKCKV_W_MAC_A, 0);
|
||||||
|
} else if(block_number == FELICA_BLOCK_INDEX_STATE) {
|
||||||
|
need_mac = felica_get_mc_bit(instance, FELICA_MC_STATE_W_MAC_A, 0);
|
||||||
|
}
|
||||||
|
return need_mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_read_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const uint8_t resp_data_index,
|
||||||
|
FelicaListenerReadCommandResponse* response) {
|
||||||
|
uint8_t num = felica_listener_get_block_index(block_number);
|
||||||
|
memcpy(
|
||||||
|
&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE],
|
||||||
|
&instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2],
|
||||||
|
FELICA_DATA_BLOCK_SIZE);
|
||||||
|
response->header.length += FELICA_DATA_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_read_all_zeros(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const uint8_t resp_data_index,
|
||||||
|
FelicaListenerReadCommandResponse* response) {
|
||||||
|
UNUSED(instance);
|
||||||
|
UNUSED(block_number);
|
||||||
|
|
||||||
|
memset(&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE], 0, FELICA_DATA_BLOCK_SIZE);
|
||||||
|
response->header.length += FELICA_DATA_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_read_mac_a_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const uint8_t resp_data_index,
|
||||||
|
FelicaListenerReadCommandResponse* response) {
|
||||||
|
if((resp_data_index != response->block_count - 1) || (resp_data_index == 0)) {
|
||||||
|
felica_handler_read_all_zeros(instance, block_number, resp_data_index, response);
|
||||||
|
instance->mac_calc_start = resp_data_index + 1;
|
||||||
|
} else {
|
||||||
|
felica_calculate_mac_read(
|
||||||
|
&instance->auth.des_context,
|
||||||
|
instance->auth.session_key.data,
|
||||||
|
instance->data->data.fs.rc.data,
|
||||||
|
&instance->requested_blocks[instance->mac_calc_start],
|
||||||
|
response->block_count - instance->mac_calc_start,
|
||||||
|
&response->data[instance->mac_calc_start * FELICA_DATA_BLOCK_SIZE],
|
||||||
|
instance->data->data.fs.mac_a.data);
|
||||||
|
felica_handler_read_block(instance, block_number, resp_data_index, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number) {
|
||||||
|
FelicaCommanReadBlockHandler handler = felica_handler_read_block;
|
||||||
|
|
||||||
|
if(block_number == FELICA_BLOCK_INDEX_RC || block_number == FELICA_BLOCK_INDEX_CK) {
|
||||||
|
handler = felica_handler_read_all_zeros;
|
||||||
|
} else if(block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||||
|
handler = felica_handler_read_mac_a_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_validate_read_block_list(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerReadRequest* const request,
|
||||||
|
FelicaCommandResponseHeader* response) {
|
||||||
|
uint8_t mac_a_pos = 0;
|
||||||
|
bool mac_a_present = false, mac_present = false;
|
||||||
|
|
||||||
|
const FelicaBlockListElement* item =
|
||||||
|
felica_listener_block_list_item_get_first(instance, request);
|
||||||
|
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||||
|
if(item->service_code != 0) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xA3;
|
||||||
|
return false;
|
||||||
|
} else if(item->access_mode != 0) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xA7;
|
||||||
|
return false;
|
||||||
|
} else if(
|
||||||
|
!felica_listener_block_list_item_validate_block_number(item) ||
|
||||||
|
!felica_block_exists(item->block_number) ||
|
||||||
|
(felica_block_is_readonly(instance, item->block_number) &&
|
||||||
|
request->base.header.service_code != FELICA_SERVICE_RO_ACCESS)) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xA8;
|
||||||
|
return false;
|
||||||
|
} else if(item->block_number == FELICA_BLOCK_INDEX_MAC) {
|
||||||
|
mac_present = true;
|
||||||
|
} else if(item->block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||||
|
if(!instance->rc_written) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xB2;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!mac_a_present) {
|
||||||
|
mac_a_present = true;
|
||||||
|
mac_a_pos = i;
|
||||||
|
}
|
||||||
|
} else if(
|
||||||
|
felica_block_requires_auth(instance, request->base.header.code, item->block_number) &&
|
||||||
|
!instance->auth.context.auth_status.external) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xB1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mac_a_present && mac_present) {
|
||||||
|
response->SF1 = (1 << mac_a_pos);
|
||||||
|
response->SF2 = 0xB0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = felica_listener_block_list_item_get_next(instance, item);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool felica_listener_validate_read_request_and_set_sf(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerReadRequest* const request,
|
||||||
|
FelicaCommandResponseHeader* resp_header) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(resp_header);
|
||||||
|
|
||||||
|
bool valid = false;
|
||||||
|
do {
|
||||||
|
if(request->base.header.service_num != 0x01) {
|
||||||
|
resp_header->SF1 = 0xFF;
|
||||||
|
resp_header->SF2 = 0xA1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if((request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) &&
|
||||||
|
(request->base.header.block_count < FELICA_LISTENER_READ_BLOCK_COUNT_MIN ||
|
||||||
|
request->base.header.block_count > FELICA_LISTENER_READ_BLOCK_COUNT_MAX)) {
|
||||||
|
resp_header->SF1 = 0xFF;
|
||||||
|
resp_header->SF2 = 0xA2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request->base.header.service_code != FELICA_SERVICE_RO_ACCESS &&
|
||||||
|
request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) {
|
||||||
|
resp_header->SF1 = 0x01;
|
||||||
|
resp_header->SF2 = 0xA6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!felica_validate_read_block_list(instance, request, resp_header)) break;
|
||||||
|
|
||||||
|
resp_header->SF1 = 0x00;
|
||||||
|
resp_header->SF2 = 0x00;
|
||||||
|
valid = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool felica_validate_write_block_list(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerWriteRequest* const request,
|
||||||
|
const FelicaListenerWriteBlockData* const data,
|
||||||
|
FelicaListenerWriteCommandResponse* response) {
|
||||||
|
const FelicaBlockListElement* items[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX] = {};
|
||||||
|
items[0] = felica_listener_block_list_item_get_first(instance, request);
|
||||||
|
items[1] = felica_listener_block_list_item_get_next(instance, items[0]);
|
||||||
|
|
||||||
|
bool write_with_mac = false;
|
||||||
|
if(request->base.header.block_count == FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX &&
|
||||||
|
items[1]->block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||||
|
write_with_mac = true;
|
||||||
|
} else if(
|
||||||
|
request->base.header.block_count < FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN ||
|
||||||
|
request->base.header.block_count > FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX) {
|
||||||
|
response->SF1 = 0x02;
|
||||||
|
response->SF2 = 0xB2;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||||
|
const FelicaBlockListElement* item = items[i];
|
||||||
|
if(!felica_listener_block_list_item_validate_block_number(item) ||
|
||||||
|
!felica_block_exists(item->block_number) ||
|
||||||
|
((i == 1) && (item->block_number != FELICA_BLOCK_INDEX_MAC_A))) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xA8;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(felica_block_is_readonly(instance, item->block_number) ||
|
||||||
|
(felica_block_requires_mac(instance, item->block_number) && !write_with_mac) ||
|
||||||
|
((i == 0) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A))) {
|
||||||
|
response->SF1 = 0x01;
|
||||||
|
response->SF2 = 0xA8;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item->service_code != 0) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xA3;
|
||||||
|
return false;
|
||||||
|
} else if(item->access_mode != 0) {
|
||||||
|
response->SF1 = (1 << i);
|
||||||
|
response->SF2 = 0xA7;
|
||||||
|
return false;
|
||||||
|
} else if((i == 1) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A)) {
|
||||||
|
uint8_t calculated_mac[8];
|
||||||
|
felica_calculate_mac_write(
|
||||||
|
&instance->auth.des_context,
|
||||||
|
instance->auth.session_key.data,
|
||||||
|
instance->data->data.fs.rc.data,
|
||||||
|
data->blocks[1].data + 8,
|
||||||
|
data->blocks[0].data,
|
||||||
|
calculated_mac);
|
||||||
|
|
||||||
|
if(!instance->rc_written || (memcmp(calculated_mac, data->blocks[1].data, 8) != 0) ||
|
||||||
|
!felica_wcnt_check_error_boundary(instance->data)) {
|
||||||
|
response->SF1 = 0x02;
|
||||||
|
response->SF2 = 0xB2;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if(
|
||||||
|
felica_block_requires_auth(instance, request->base.header.code, item->block_number) &&
|
||||||
|
!instance->auth.context.auth_status.external) {
|
||||||
|
response->SF1 = 0x01;
|
||||||
|
response->SF2 = 0xB1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool felica_listener_validate_write_request_and_set_sf(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerWriteRequest* const request,
|
||||||
|
const FelicaListenerWriteBlockData* const data,
|
||||||
|
FelicaListenerWriteCommandResponse* response) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(data);
|
||||||
|
furi_assert(response);
|
||||||
|
|
||||||
|
bool valid = false;
|
||||||
|
do {
|
||||||
|
if(request->base.header.service_num != 0x01) {
|
||||||
|
response->SF1 = 0xFF;
|
||||||
|
response->SF2 = 0xA1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) &&
|
||||||
|
(request->base.header.block_count < 0x01 || request->base.header.block_count > 0x02)) {
|
||||||
|
response->SF1 = 0xFF;
|
||||||
|
response->SF2 = 0xA2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) {
|
||||||
|
response->SF1 = 0x01;
|
||||||
|
response->SF2 = 0xA6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!felica_validate_write_block_list(instance, request, data, response)) break;
|
||||||
|
|
||||||
|
if(felica_wcnt_check_warning_boundary(instance->data)) {
|
||||||
|
response->SF1 = 0xFF;
|
||||||
|
response->SF2 = 0x71;
|
||||||
|
} else {
|
||||||
|
response->SF1 = 0x00;
|
||||||
|
response->SF2 = 0x00;
|
||||||
|
}
|
||||||
|
valid = true;
|
||||||
|
} while(false);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_write_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block) {
|
||||||
|
uint8_t num = felica_listener_get_block_index(block_number);
|
||||||
|
|
||||||
|
memcpy(
|
||||||
|
&instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2],
|
||||||
|
data_block->data,
|
||||||
|
FELICA_DATA_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_write_rc_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block) {
|
||||||
|
felica_handler_write_block(instance, block_number, data_block);
|
||||||
|
|
||||||
|
felica_calculate_session_key(
|
||||||
|
&instance->auth.des_context,
|
||||||
|
instance->data->data.fs.ck.data,
|
||||||
|
instance->data->data.fs.rc.data,
|
||||||
|
instance->auth.session_key.data);
|
||||||
|
instance->rc_written = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_write_reg_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block) {
|
||||||
|
UNUSED(block_number);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t A;
|
||||||
|
uint32_t B;
|
||||||
|
uint64_t C;
|
||||||
|
} FELICA_REG_BLOCK;
|
||||||
|
|
||||||
|
FELICA_REG_BLOCK* Reg = (FELICA_REG_BLOCK*)instance->data->data.fs.reg.data;
|
||||||
|
FELICA_REG_BLOCK* RegNew = (FELICA_REG_BLOCK*)data_block->data;
|
||||||
|
|
||||||
|
if(Reg->A >= RegNew->A) {
|
||||||
|
Reg->A = RegNew->A;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Reg->B >= RegNew->B) {
|
||||||
|
Reg->B = RegNew->B;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((Reg->A >= RegNew->A) && (Reg->B >= RegNew->B)) {
|
||||||
|
Reg->C = RegNew->C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_write_mc_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block) {
|
||||||
|
UNUSED(block_number);
|
||||||
|
|
||||||
|
bool mc_bits_permission = felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, 15);
|
||||||
|
const FelicaData* data = instance->data;
|
||||||
|
bool mc_system_block_permission = FELICA_SYSTEM_BLOCK_RW_ACCESS(data);
|
||||||
|
for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
|
||||||
|
if((i <= 1) && mc_bits_permission) {
|
||||||
|
instance->mc_shadow.data[i] &= data_block->data[i];
|
||||||
|
} else if(
|
||||||
|
(i >= FELICA_MC_ALL_BYTE && i <= FELICA_MC_CKCKV_W_MAC_A) &&
|
||||||
|
(mc_system_block_permission)) {
|
||||||
|
instance->mc_shadow.data[i] = data_block->data[i];
|
||||||
|
} else if(
|
||||||
|
(i >= FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 && i <= FELICA_MC_STATE_W_MAC_A) &&
|
||||||
|
(mc_bits_permission)) {
|
||||||
|
instance->mc_shadow.data[i] |= data_block->data[i];
|
||||||
|
} else if(i >= FELICA_MC_RESERVED_13) {
|
||||||
|
instance->mc_shadow.data[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_write_state_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block) {
|
||||||
|
felica_handler_write_block(instance, block_number, data_block);
|
||||||
|
bool state = instance->data->data.fs.state.data[0] == 0x01;
|
||||||
|
instance->auth.context.auth_status.external = state;
|
||||||
|
instance->auth.context.auth_status.internal = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void felica_handler_write_id_block(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block) {
|
||||||
|
UNUSED(block_number);
|
||||||
|
memcpy(&instance->data->data.fs.id.data[8], &data_block->data[8], 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
FelicaCommandWriteBlockHandler
|
||||||
|
felica_listener_get_write_block_handler(const uint8_t block_number) {
|
||||||
|
FelicaCommandWriteBlockHandler handler = felica_handler_write_block;
|
||||||
|
switch(block_number) {
|
||||||
|
case FELICA_BLOCK_INDEX_RC:
|
||||||
|
handler = felica_handler_write_rc_block;
|
||||||
|
break;
|
||||||
|
case FELICA_BLOCK_INDEX_REG:
|
||||||
|
handler = felica_handler_write_reg_block;
|
||||||
|
break;
|
||||||
|
case FELICA_BLOCK_INDEX_MC:
|
||||||
|
handler = felica_handler_write_mc_block;
|
||||||
|
break;
|
||||||
|
case FELICA_BLOCK_INDEX_STATE:
|
||||||
|
handler = felica_handler_write_state_block;
|
||||||
|
break;
|
||||||
|
case FELICA_BLOCK_INDEX_ID:
|
||||||
|
handler = felica_handler_write_id_block;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handler = felica_handler_write_block;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FelicaError felica_listener_process_error(NfcError error) {
|
||||||
|
switch(error) {
|
||||||
|
case NfcErrorNone:
|
||||||
|
return FelicaErrorNone;
|
||||||
|
case NfcErrorTimeout:
|
||||||
|
return FelicaErrorTimeout;
|
||||||
|
default:
|
||||||
|
return FelicaErrorNotPresent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FelicaError
|
||||||
|
felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(tx_buffer);
|
||||||
|
|
||||||
|
const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer);
|
||||||
|
furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE);
|
||||||
|
|
||||||
|
felica_crc_append(instance->tx_buffer);
|
||||||
|
|
||||||
|
FelicaError ret = FelicaErrorNone;
|
||||||
|
|
||||||
|
do {
|
||||||
|
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||||
|
if(error != NfcErrorNone) {
|
||||||
|
ret = felica_listener_process_error(error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
@ -2,15 +2,72 @@
|
|||||||
|
|
||||||
#include <nfc/protocols/nfc_generic_event.h>
|
#include <nfc/protocols/nfc_generic_event.h>
|
||||||
|
|
||||||
|
#define FELICA_LISTENER_READ_BLOCK_COUNT_MAX (4U)
|
||||||
|
#define FELICA_LISTENER_READ_BLOCK_COUNT_MIN (1U)
|
||||||
|
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U)
|
||||||
|
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
Felica_ListenerStateIdle,
|
Felica_ListenerStateIdle,
|
||||||
Felica_ListenerStateActivated,
|
Felica_ListenerStateActivated,
|
||||||
} FelicaListenerState;
|
} FelicaListenerState;
|
||||||
|
|
||||||
|
/** Generic Felica request same for both read and write operations. */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t length;
|
||||||
|
FelicaCommandHeader header;
|
||||||
|
} FelicaListenerGenericRequest;
|
||||||
|
|
||||||
|
/** Generic request but with list of requested elements. */
|
||||||
|
typedef struct {
|
||||||
|
FelicaListenerGenericRequest base;
|
||||||
|
FelicaBlockListElement list[];
|
||||||
|
} FelicaListenerRequest;
|
||||||
|
|
||||||
|
typedef FelicaListenerRequest FelicaListenerReadRequest;
|
||||||
|
typedef FelicaListenerRequest FelicaListenerWriteRequest;
|
||||||
|
|
||||||
|
/** Struct for write request data.
|
||||||
|
*
|
||||||
|
* All data are placed right after @ref FelicaListenerRequest data.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
FelicaBlockData blocks[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX];
|
||||||
|
} FelicaListenerWriteBlockData;
|
||||||
|
|
||||||
|
/** Write command handler signature.
|
||||||
|
*
|
||||||
|
* Depending on requested block write behaviour can be different, therefore
|
||||||
|
* different handlers are used.
|
||||||
|
*/
|
||||||
|
typedef void (*FelicaCommandWriteBlockHandler)(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const FelicaBlockData* data_block);
|
||||||
|
|
||||||
|
/** Read command handler signature.
|
||||||
|
*
|
||||||
|
* Depending on requested block read behaviour can be different, therefore
|
||||||
|
* different handlers are used.
|
||||||
|
*/
|
||||||
|
typedef void (*FelicaCommanReadBlockHandler)(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const uint8_t block_number,
|
||||||
|
const uint8_t resp_data_index,
|
||||||
|
FelicaListenerReadCommandResponse* response);
|
||||||
|
|
||||||
struct FelicaListener {
|
struct FelicaListener {
|
||||||
Nfc* nfc;
|
Nfc* nfc;
|
||||||
FelicaData* data;
|
FelicaData* data;
|
||||||
FelicaListenerState state;
|
FelicaListenerState state;
|
||||||
|
FelicaAuthentication auth;
|
||||||
|
FelicaBlockData mc_shadow;
|
||||||
|
|
||||||
|
uint8_t request_size_buf;
|
||||||
|
uint8_t block_list_size;
|
||||||
|
uint8_t requested_blocks[FELICA_LISTENER_READ_BLOCK_COUNT_MAX];
|
||||||
|
uint8_t mac_calc_start;
|
||||||
|
bool rc_written;
|
||||||
|
|
||||||
BitBuffer* tx_buffer;
|
BitBuffer* tx_buffer;
|
||||||
BitBuffer* rx_buffer;
|
BitBuffer* rx_buffer;
|
||||||
@ -19,3 +76,169 @@ struct FelicaListener {
|
|||||||
NfcGenericCallback callback;
|
NfcGenericCallback callback;
|
||||||
void* context;
|
void* context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Resets card authentication state after field off, also resets session key and
|
||||||
|
* perform post process operations with some blocks.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
*/
|
||||||
|
void felica_listener_reset(FelicaListener* instance);
|
||||||
|
|
||||||
|
/** Performs WCNT increasing in case of write operation.
|
||||||
|
*
|
||||||
|
* @param data pointer to Felica card data.
|
||||||
|
*/
|
||||||
|
void felica_wcnt_increment(FelicaData* data);
|
||||||
|
|
||||||
|
/** Compares IDm of card loaded for emulation with IDm from request.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param request_idm pointer to IDm block from request structure.
|
||||||
|
*
|
||||||
|
* @return True if IDms' are equal, otherwise false.
|
||||||
|
*/
|
||||||
|
bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm);
|
||||||
|
|
||||||
|
/** This is the first request validation function.
|
||||||
|
*
|
||||||
|
* Its main aim is to check whether the input request is valid in general by
|
||||||
|
* counting the amount of data in request. Function iterates through block list
|
||||||
|
* elements and data, counts real amount of elements and real amount of
|
||||||
|
* coresponding elements data (in case of write operation). If block list
|
||||||
|
* element amount is invalid or request data is missing, such request will be
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param request pointer to received request.
|
||||||
|
*
|
||||||
|
* @return True if block element list contains valid amount of data,
|
||||||
|
* otherwise false.
|
||||||
|
*/
|
||||||
|
bool felica_listener_check_block_list_size(
|
||||||
|
FelicaListener* instance,
|
||||||
|
FelicaListenerGenericRequest* request);
|
||||||
|
|
||||||
|
/** Used to take first element from block element list.
|
||||||
|
*
|
||||||
|
* Each element in block list can be 2 or 3-bytes length and they can be mixed
|
||||||
|
* together. Therefore before returning the element, function checks whether
|
||||||
|
* there is enough bytes in the request for this element, in order to avoid
|
||||||
|
* memory casting behind the request block.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param request pointer to received request.
|
||||||
|
*
|
||||||
|
* @return Pointer to the first element of the list or NULL if there is not
|
||||||
|
* enough bytes in the request.
|
||||||
|
*/
|
||||||
|
const FelicaBlockListElement* felica_listener_block_list_item_get_first(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerRequest* request);
|
||||||
|
|
||||||
|
/** Used to take next element from block element list.
|
||||||
|
*
|
||||||
|
* It uses pointer to the previous element, takes its length and calculates
|
||||||
|
* pointer to the next element if there is enough bytes left.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param prev_item pointer to the previous item taken.
|
||||||
|
*
|
||||||
|
* @return Pointer to the next element of the list or NULL if there is not
|
||||||
|
* enough bytes in the request.
|
||||||
|
*/
|
||||||
|
const FelicaBlockListElement* felica_listener_block_list_item_get_next(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaBlockListElement* prev_item);
|
||||||
|
|
||||||
|
/** Calculates pointer to data blocks in case of write operation, because block
|
||||||
|
* list elements size can vary.
|
||||||
|
*
|
||||||
|
* For calculation it uses previously calculated block list size and
|
||||||
|
* FelicaListenerGenericRequest size.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param generic_request pointer to received request.
|
||||||
|
*
|
||||||
|
* @return Pointer to data blocks for write operation.
|
||||||
|
*/
|
||||||
|
const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer(
|
||||||
|
const FelicaListener* const instance,
|
||||||
|
const FelicaListenerGenericRequest* const generic_request);
|
||||||
|
|
||||||
|
/** Function validates write request data and sets Felica SF1 and SF2 flags
|
||||||
|
* directly to the response.
|
||||||
|
*
|
||||||
|
* When something is wrong with data in the request (for example non-existing
|
||||||
|
* block is requested) this function sets proper SF1 and SF2 flags which will be
|
||||||
|
* then send to reader. When every thing is fine SF1 and SF2 are 0. Also this
|
||||||
|
* function performs authentiocation checks by MAC_A calculation if request
|
||||||
|
* contains MAC_A.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param request pointer to received write request.
|
||||||
|
* @param data pointer to data which request wants to write.
|
||||||
|
* @param response pointer to the response to where SF1 and SF2 flags will
|
||||||
|
* be populated.
|
||||||
|
*
|
||||||
|
* @return True if request is fine also SF1 and SF2 will be 0. False if
|
||||||
|
* something is wrong and SF1, SF2 will provide more detailed
|
||||||
|
* information about the error.
|
||||||
|
*/
|
||||||
|
bool felica_listener_validate_write_request_and_set_sf(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerWriteRequest* const request,
|
||||||
|
const FelicaListenerWriteBlockData* const data,
|
||||||
|
FelicaListenerWriteCommandResponse* response);
|
||||||
|
|
||||||
|
/** Function validates read request data and sets Felica SF1 and SF2 flags
|
||||||
|
* directly to the response.
|
||||||
|
*
|
||||||
|
* When something is wrong with data in the request (for example non-existing
|
||||||
|
* block is requested) this function sets proper SF1 and SF2 flags which will be
|
||||||
|
* then send to reader. In such case there will be no any data in the request,
|
||||||
|
* only flags. In case when request is fine SF1 and SF2 will be 0 and data will
|
||||||
|
* be populated to the response after them. Attention! This function doesn't
|
||||||
|
* populate any data, it only allows further processing where those data will be
|
||||||
|
* populated.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param request pointer to received write request.
|
||||||
|
* @param resp_header pointer to the response to where SF1 and SF2 and
|
||||||
|
* data flags will be populated.
|
||||||
|
*
|
||||||
|
* @return True if request is fine also SF1 and SF2 will be 0. False if
|
||||||
|
* something is wrong and SF1, SF2 will provide more detailed
|
||||||
|
* information about the error.
|
||||||
|
*/
|
||||||
|
bool felica_listener_validate_read_request_and_set_sf(
|
||||||
|
FelicaListener* instance,
|
||||||
|
const FelicaListenerReadRequest* const request,
|
||||||
|
FelicaCommandResponseHeader* resp_header);
|
||||||
|
|
||||||
|
/** Function returns appropiate block handler for processing read operation
|
||||||
|
* depending on number of block.
|
||||||
|
*
|
||||||
|
* @param block_number number of block.
|
||||||
|
*
|
||||||
|
* @return pointer to block handler function.
|
||||||
|
*/
|
||||||
|
FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number);
|
||||||
|
|
||||||
|
/** Function returns appropiate block handler for processing write operation
|
||||||
|
* depending on number of block.
|
||||||
|
*
|
||||||
|
* @param block_number number of block.
|
||||||
|
*
|
||||||
|
* @return pointer to block handler function.
|
||||||
|
*/
|
||||||
|
FelicaCommandWriteBlockHandler felica_listener_get_write_block_handler(const uint8_t block_number);
|
||||||
|
|
||||||
|
/** Sends response data from buffer to reader.
|
||||||
|
*
|
||||||
|
* @param instance pointer to the listener instance to be used.
|
||||||
|
* @param tx_buffer buffer with response data.
|
||||||
|
*
|
||||||
|
* @return error code or FelicaErrorNone.
|
||||||
|
*/
|
||||||
|
FelicaError
|
||||||
|
felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer);
|
||||||
|
@ -24,22 +24,6 @@ typedef enum {
|
|||||||
FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */
|
FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */
|
||||||
} FelicaPollerEventType;
|
} FelicaPollerEventType;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Stucture for holding Felica session key which is calculated from rc and ck.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
|
||||||
} FelicaSessionKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Structure used to hold authentication related fields.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */
|
|
||||||
FelicaSessionKey session_key; /**< Calculated session key. */
|
|
||||||
FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */
|
|
||||||
} FelicaAuthentication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Felica poller event data.
|
* @brief Felica poller event data.
|
||||||
*/
|
*/
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
#include <nfc/helpers/felica_crc.h>
|
#include <nfc/helpers/felica_crc.h>
|
||||||
|
|
||||||
#define TAG "FelicaPoller"
|
#define TAG "FelicaPoller"
|
||||||
#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U)
|
|
||||||
#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U)
|
|
||||||
|
|
||||||
#define FELICA_SERVICE_RW_ACCESS (0x0009U)
|
|
||||||
#define FELICA_SERVICE_RO_ACCESS (0x000BU)
|
|
||||||
|
|
||||||
static FelicaError felica_poller_process_error(NfcError error) {
|
static FelicaError felica_poller_process_error(NfcError error) {
|
||||||
switch(error) {
|
switch(error) {
|
||||||
|
@ -55,41 +55,6 @@ typedef struct {
|
|||||||
uint8_t request_data[2];
|
uint8_t request_data[2];
|
||||||
} FelicaPollerPollingResponse;
|
} FelicaPollerPollingResponse;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t service_code : 4;
|
|
||||||
uint8_t access_mode : 3;
|
|
||||||
uint8_t length : 1;
|
|
||||||
uint8_t block_number;
|
|
||||||
} FelicaBlockListElement;
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
typedef struct {
|
|
||||||
uint8_t code;
|
|
||||||
FelicaIDm idm;
|
|
||||||
uint8_t service_num;
|
|
||||||
uint16_t service_code;
|
|
||||||
uint8_t block_count;
|
|
||||||
} FelicaCommandHeader;
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t length;
|
|
||||||
uint8_t response_code;
|
|
||||||
FelicaIDm idm;
|
|
||||||
uint8_t SF1;
|
|
||||||
uint8_t SF2;
|
|
||||||
uint8_t block_count;
|
|
||||||
uint8_t data[];
|
|
||||||
} FelicaPollerReadCommandResponse;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t length;
|
|
||||||
uint8_t response_code;
|
|
||||||
FelicaIDm idm;
|
|
||||||
uint8_t SF1;
|
|
||||||
uint8_t SF2;
|
|
||||||
} FelicaPollerWriteCommandResponse;
|
|
||||||
|
|
||||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
70
lib/nfc/protocols/felica/felica_poller_sync.c
Normal file
70
lib/nfc/protocols/felica/felica_poller_sync.c
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#include "felica_poller_sync.h"
|
||||||
|
|
||||||
|
#include "felica_poller_i.h"
|
||||||
|
#include <nfc/nfc_poller.h>
|
||||||
|
|
||||||
|
#include <furi/furi.h>
|
||||||
|
|
||||||
|
#define FELICA_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FelicaAuthenticationContext auth_ctx;
|
||||||
|
FuriThreadId thread_id;
|
||||||
|
FelicaError error;
|
||||||
|
FelicaData data;
|
||||||
|
} Felica_PollerContext;
|
||||||
|
|
||||||
|
NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(event.event_data);
|
||||||
|
furi_assert(event.instance);
|
||||||
|
furi_assert(event.protocol == NfcProtocolFelica);
|
||||||
|
|
||||||
|
Felica_PollerContext* poller_context = context;
|
||||||
|
FelicaPoller* felica_poller = event.instance;
|
||||||
|
|
||||||
|
FelicaPollerEvent* felica_event = event.event_data;
|
||||||
|
|
||||||
|
if(felica_event->type == FelicaPollerEventTypeReady ||
|
||||||
|
felica_event->type == FelicaPollerEventTypeIncomplete) {
|
||||||
|
felica_copy(&poller_context->data, felica_poller->data);
|
||||||
|
} else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
|
||||||
|
felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth;
|
||||||
|
memcpy(
|
||||||
|
felica_event->data->auth_context->card_key.data,
|
||||||
|
poller_context->auth_ctx.card_key.data,
|
||||||
|
FELICA_DATA_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_thread_flags_set(poller_context->thread_id, FELICA_POLLER_FLAG_COMMAND_COMPLETE);
|
||||||
|
|
||||||
|
return NfcCommandStop;
|
||||||
|
}
|
||||||
|
|
||||||
|
FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key) {
|
||||||
|
furi_check(nfc);
|
||||||
|
furi_check(data);
|
||||||
|
|
||||||
|
Felica_PollerContext poller_context = {};
|
||||||
|
if(card_key == NULL) {
|
||||||
|
poller_context.auth_ctx.skip_auth = true;
|
||||||
|
} else {
|
||||||
|
poller_context.auth_ctx.skip_auth = false;
|
||||||
|
memcpy(poller_context.auth_ctx.card_key.data, card_key->data, FELICA_DATA_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
poller_context.thread_id = furi_thread_get_current_id();
|
||||||
|
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolFelica);
|
||||||
|
nfc_poller_start(poller, felica_poller_read_callback, &poller_context);
|
||||||
|
furi_thread_flags_wait(FELICA_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever);
|
||||||
|
furi_thread_flags_clear(FELICA_POLLER_FLAG_COMMAND_COMPLETE);
|
||||||
|
|
||||||
|
nfc_poller_stop(poller);
|
||||||
|
nfc_poller_free(poller);
|
||||||
|
|
||||||
|
if(poller_context.error == FelicaErrorNone) {
|
||||||
|
*data = poller_context.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return poller_context.error;
|
||||||
|
}
|
14
lib/nfc/protocols/felica/felica_poller_sync.h
Normal file
14
lib/nfc/protocols/felica/felica_poller_sync.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "felica.h"
|
||||||
|
#include <nfc/nfc.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
@ -224,6 +224,11 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header,
|
|||||||
|
|
||||||
FuriString* full_extracted_fname;
|
FuriString* full_extracted_fname;
|
||||||
if(header->type == MTAR_TDIR) {
|
if(header->type == MTAR_TDIR) {
|
||||||
|
// Skip "/" entry since concat would leave it dangling, also want caller to mkdir destination
|
||||||
|
if(strcmp(header->name, "/") == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
full_extracted_fname = furi_string_alloc();
|
full_extracted_fname = furi_string_alloc();
|
||||||
path_concat(op_params->work_dir, header->name, full_extracted_fname);
|
path_concat(op_params->work_dir, header->name, full_extracted_fname);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,64.0,,
|
Version,+,64.2,,
|
||||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
Header,+,applications/services/cli/cli.h,,
|
Header,+,applications/services/cli/cli.h,,
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,64.0,,
|
Version,+,64.2,,
|
||||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
@ -138,7 +138,9 @@ Header,+,lib/nfc/nfc_scanner.h,,
|
|||||||
Header,+,lib/nfc/protocols/emv/emv.h,,
|
Header,+,lib/nfc/protocols/emv/emv.h,,
|
||||||
Header,+,lib/nfc/protocols/emv/emv_poller.h,,
|
Header,+,lib/nfc/protocols/emv/emv_poller.h,,
|
||||||
Header,+,lib/nfc/protocols/felica/felica.h,,
|
Header,+,lib/nfc/protocols/felica/felica.h,,
|
||||||
|
Header,+,lib/nfc/protocols/felica/felica_listener.h,,
|
||||||
Header,+,lib/nfc/protocols/felica/felica_poller.h,,
|
Header,+,lib/nfc/protocols/felica/felica_poller.h,,
|
||||||
|
Header,+,lib/nfc/protocols/felica/felica_poller_sync.h,,
|
||||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,,
|
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,,
|
||||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,,
|
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,,
|
||||||
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,,
|
Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,,
|
||||||
@ -1038,6 +1040,7 @@ Function,-,fdimf,float,"float, float"
|
|||||||
Function,-,fdiml,long double,"long double, long double"
|
Function,-,fdiml,long double,"long double, long double"
|
||||||
Function,-,fdopen,FILE*,"int, const char*"
|
Function,-,fdopen,FILE*,"int, const char*"
|
||||||
Function,+,felica_alloc,FelicaData*,
|
Function,+,felica_alloc,FelicaData*,
|
||||||
|
Function,+,felica_calculate_mac_read,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, const uint8_t*, uint8_t*"
|
||||||
Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*"
|
Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*"
|
||||||
Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*"
|
Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*"
|
||||||
Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*"
|
Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*"
|
||||||
@ -1049,6 +1052,7 @@ Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*"
|
|||||||
Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*"
|
Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*"
|
||||||
Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t"
|
Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t"
|
||||||
Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*"
|
Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*"
|
||||||
|
Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*"
|
||||||
Function,+,felica_reset,void,FelicaData*
|
Function,+,felica_reset,void,FelicaData*
|
||||||
Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*"
|
Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*"
|
||||||
Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t"
|
Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t"
|
||||||
|
|
@ -77,6 +77,9 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) {
|
|||||||
if(irq & ST25R3916_IRQ_MASK_WU_A_X) {
|
if(irq & ST25R3916_IRQ_MASK_WU_A_X) {
|
||||||
event |= FuriHalNfcEventListenerActive;
|
event |= FuriHalNfcEventListenerActive;
|
||||||
}
|
}
|
||||||
|
if(irq & ST25R3916_IRQ_MASK_WU_F) {
|
||||||
|
event |= FuriHalNfcEventListenerActive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) {
|
if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) {
|
||||||
event |= FuriHalNfcEventTimerFwtExpired;
|
event |= FuriHalNfcEventTimerFwtExpired;
|
||||||
|
@ -55,6 +55,7 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha
|
|||||||
|
|
||||||
static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) {
|
static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) {
|
||||||
furi_assert(handle);
|
furi_assert(handle);
|
||||||
|
st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT);
|
||||||
st25r3916_write_reg(
|
st25r3916_write_reg(
|
||||||
handle,
|
handle,
|
||||||
ST25R3916_REG_OP_CONTROL,
|
ST25R3916_REG_OP_CONTROL,
|
||||||
@ -89,8 +90,8 @@ static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* ha
|
|||||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00);
|
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00);
|
||||||
// No gain reduction on AM and PM channels
|
// No gain reduction on AM and PM channels
|
||||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
|
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
|
||||||
// 10% ASK modulation
|
// 40% ASK modulation
|
||||||
st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent);
|
st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_40percent);
|
||||||
|
|
||||||
// Correlator setup
|
// Correlator setup
|
||||||
st25r3916_write_reg(
|
st25r3916_write_reg(
|
||||||
@ -142,9 +143,7 @@ FuriHalNfcError furi_hal_nfc_felica_listener_tx(
|
|||||||
FuriHalSpiBusHandle* handle,
|
FuriHalSpiBusHandle* handle,
|
||||||
const uint8_t* tx_data,
|
const uint8_t* tx_data,
|
||||||
size_t tx_bits) {
|
size_t tx_bits) {
|
||||||
UNUSED(handle);
|
furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits);
|
||||||
UNUSED(tx_data);
|
|
||||||
UNUSED(tx_bits);
|
|
||||||
return FuriHalNfcErrorNone;
|
return FuriHalNfcErrorNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user