Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX 2024-06-09 16:05:06 +03:00
commit 7f05a224be
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
26 changed files with 1538 additions and 138 deletions

View file

@ -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

View file

@ -12,6 +12,8 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_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/iso15693_3/iso15693_3_poller.h>
#include <nfc/protocols/slix/slix.h>
@ -646,6 +648,56 @@ MU_TEST(mf_classic_dict_test) {
"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) {
NfcDevice* nfc_device_missed_cap = nfc_device_alloc();
mu_assert(
@ -807,6 +859,8 @@ MU_TEST_SUITE(nfc) {
MU_RUN_TEST(mf_classic_value_block);
MU_RUN_TEST(mf_classic_send_frame_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_set_password_default_cap_correct_pass);

View file

@ -26,12 +26,6 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
widget_add_text_scroll_element(
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);
}
@ -177,7 +171,7 @@ static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEv
}
const NfcProtocolSupportBase nfc_protocol_support_felica = {
.features = NfcProtocolFeatureEmulateUid,
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
.scene_info =
{

View file

@ -52,6 +52,7 @@ typedef enum {
SubGhzRxKeyStateAddKey,
SubGhzRxKeyStateExit,
SubGhzRxKeyStateRAWLoad,
SubGhzRxKeyStateRAWMore,
SubGhzRxKeyStateRAWSave,
} SubGhzRxKeyState;

View file

@ -66,7 +66,13 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) {
if(event.event == SubGhzCustomEventSceneDeleteRAW) {
furi_string_set(subghz->file_path_tmp, subghz->file_path);
if(subghz_delete_file(subghz)) {
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 {
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneStart);

View file

@ -80,6 +80,7 @@ void subghz_scene_read_raw_on_enter(void* context) {
subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, "", threshold_rssi);
break;
case SubGhzRxKeyStateRAWLoad:
case SubGhzRxKeyStateRAWMore:
path_extract_filename(subghz->file_path, file_name, true);
subghz_read_raw_set_status(
subghz->subghz_read_raw,
@ -101,7 +102,8 @@ void subghz_scene_read_raw_on_enter(void* context) {
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);
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)) {
scene_manager_set_scene_state(
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);
consumed = true;
} else {

View file

@ -8,6 +8,7 @@
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/path.h>
#include <update_util/lfs_backup.h>
#include <toolbox/tar/tar_archive.h>
#include <pb_decode.h>
#include <storage.pb.h>
@ -50,7 +51,6 @@ static void rpc_system_storage_reset_state(
if(rpc_storage->state == RpcStorageStateWriting) {
storage_file_close(rpc_storage->file);
storage_file_free(rpc_storage->file);
furi_record_close(RECORD_STORAGE);
}
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));
response->command_id = request->command_id;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_fs_info(
fs_api,
rpc_storage->api,
request->content.storage_info_request.path,
&response->content.storage_info_response.total_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);
free(response);
furi_record_close(RECORD_STORAGE);
}
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));
response->command_id = request->command_id;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
const char* path = request->content.storage_timestamp_request.path;
uint32_t timestamp = 0;
FS_Error error = storage_common_timestamp(fs_api, path, &timestamp);
FS_Error error = storage_common_timestamp(rpc_storage->api, path, &timestamp);
response->command_status = rpc_system_storage_get_error(error);
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);
free(response);
furi_record_close(RECORD_STORAGE);
}
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));
response->command_id = request->command_id;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
const char* path = request->content.storage_stat_request.path;
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->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);
free(response);
furi_record_close(RECORD_STORAGE);
}
static void rpc_system_storage_list_root(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
@ -279,8 +271,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
return;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* dir = storage_file_alloc(fs_api);
File* dir = storage_file_alloc(rpc_storage->api);
PB_Main response = {
.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;
FuriString* md5 = 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;
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_file_free(dir);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
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 */
PB_Main* response = malloc(sizeof(PB_Main));
const char* path = request->content.storage_read_request.path;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(fs_api);
File* file = storage_file_alloc(rpc_storage->api);
bool fs_operation_success = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING);
if(fs_operation_success) {
@ -420,8 +408,6 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex
free(response);
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
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) {
rpc_storage->api = furi_record_open(RECORD_STORAGE);
rpc_storage->file = storage_file_alloc(rpc_storage->api);
rpc_storage->current_command_id = request->command_id;
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) {
furi_assert(fs_api);
static bool rpc_system_storage_is_dir_is_empty(Storage* storage, const char* path) {
furi_assert(storage);
furi_assert(path);
FileInfo fileinfo;
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)) {
File* dir = storage_file_alloc(fs_api);
File* dir = storage_file_alloc(storage);
if(storage_dir_open(dir, path)) {
char* name = malloc(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;
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;
if(!path) {
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
} 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
// 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) {
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;
} else {
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);
}
@ -572,11 +556,10 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte
PB_CommandStatus status;
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;
if(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);
} else {
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 {
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
}
furi_record_close(RECORD_STORAGE);
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;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(fs_api);
File* file = storage_file_alloc(rpc_storage->api);
FuriString* md5 = furi_string_alloc();
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);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
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;
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)) {
FS_Error error = storage_common_rename(
fs_api,
rpc_storage->api,
request->content.storage_rename_request.old_path,
request->content.storage_rename_request.new_path);
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;
}
furi_record_close(RECORD_STORAGE);
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;
furi_assert(session);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
rpc_system_storage_reset_state(rpc_storage, session, true);
bool backup_ok =
lfs_backup_create(fs_api, request->content.storage_backup_create_request.archive_path);
furi_record_close(RECORD_STORAGE);
bool backup_ok = lfs_backup_create(
rpc_storage->api, request->content.storage_backup_create_request.archive_path);
rpc_send_and_release_empty(
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;
furi_assert(session);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
rpc_system_storage_reset_state(rpc_storage, session, true);
bool backup_ok =
lfs_backup_unpack(fs_api, request->content.storage_backup_restore_request.archive_path);
furi_record_close(RECORD_STORAGE);
bool backup_ok = lfs_backup_unpack(
rpc_storage->api, request->content.storage_backup_restore_request.archive_path);
rpc_send_and_release_empty(
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) {
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_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;
}
@ -771,5 +789,8 @@ void rpc_system_storage_free(void* context) {
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, false);
furi_record_close(RECORD_STORAGE);
rpc_storage->api = NULL;
free(rpc_storage);
}

@ -1 +1 @@
Subproject commit 1956b83bba99313ee8d8386e5d35d0549341ca26
Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a

View file

@ -45,11 +45,13 @@ env.Append(
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
File("protocols/mf_classic/mf_classic_listener.h"),
File("protocols/felica/felica_listener.h"),
# Sync API
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"),
File("protocols/mf_classic/mf_classic_poller_sync.h"),
File("protocols/st25tb/st25tb_poller_sync.h"),
File("protocols/felica/felica_poller_sync.h"),
# Misc
File("helpers/nfc_util.h"),
File("helpers/iso14443_crc.h"),

View file

@ -6,6 +6,8 @@
#define FELICA_CRC_INIT (0x0000U)
uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
furi_check(data);
uint16_t crc = FELICA_CRC_INIT;
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) {
furi_check(buf);
const uint8_t* data = bit_buffer_get_data(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) {
furi_check(buf);
const size_t data_size = bit_buffer_get_size_bytes(buf);
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) {
furi_check(buf);
const size_t data_size = bit_buffer_get_size_bytes(buf);
furi_assert(data_size > FELICA_CRC_SIZE);

View file

@ -3,6 +3,9 @@
#include <lib/nfc/nfc.h>
#include <lib/nfc/helpers/iso14443_crc.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>
@ -50,11 +53,31 @@ typedef struct {
Iso14443_3aSelResp sel_resp[2];
} 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 {
NfcState state;
Iso14443_3aColResStatus col_res_status;
Iso14443_3aColResData col_res_data;
FelicaPTMemory pt_memory;
bool software_col_res_required;
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};
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;
}
}
@ -470,6 +508,12 @@ NfcError nfc_felica_listener_set_sensf_res_data(
furi_assert(idm_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;
}

View file

@ -298,15 +298,33 @@ bool felica_check_mac(
furi_check(blocks);
furi_check(data);
uint8_t first_block[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);
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(

View file

@ -12,7 +12,13 @@ extern "C" {
#define FELICA_PMM_SIZE (8U)
#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_RC (0x80U)
#define FELICA_BLOCK_INDEX_MAC (0x81U)
@ -54,6 +60,10 @@ typedef enum {
FelicaErrorTimeout,
} FelicaError;
typedef struct {
uint8_t data[FELICA_DATA_BLOCK_SIZE];
} FelicaBlockData;
/** @brief Separate type for card key block. Used in authentication process */
typedef struct {
uint8_t data[FELICA_DATA_BLOCK_SIZE];
@ -76,6 +86,22 @@ typedef struct {
FelicaAuthenticationStatus auth_status; /**< Authentication status*/
} 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 */
typedef struct {
uint8_t data[FELICA_IDM_SIZE];
@ -128,6 +154,51 @@ typedef struct {
FelicaFSUnion data;
} 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;
FelicaData* felica_alloc(void);
@ -168,6 +239,15 @@ bool felica_check_mac(
const uint8_t block_count,
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(
mbedtls_des3_context* ctx,
const uint8_t* session_key,

View file

@ -1,9 +1,14 @@
#include "felica_listener_i.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 TAG "Felica"
#define FELICA_LISTENER_MAX_BUFFER_SIZE (128)
#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) {
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->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);
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_felica_listener_set_sensf_res_data(
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;
}
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) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolInvalid);
@ -58,14 +159,44 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
NfcEvent* nfc_event = event.event_data;
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;
FURI_LOG_D(TAG, "Activated");
} else if(nfc_event->type == NfcEventTypeFieldOff) {
instance->state = Felica_ListenerStateIdle;
FURI_LOG_D(TAG, "Field Off");
felica_listener_reset(instance);
} else if(nfc_event->type == NfcEventTypeRxEnd) {
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;
}

View 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;
}

View file

@ -2,15 +2,72 @@
#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 {
Felica_ListenerStateIdle,
Felica_ListenerStateActivated,
} 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 {
Nfc* nfc;
FelicaData* data;
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* rx_buffer;
@ -19,3 +76,169 @@ struct FelicaListener {
NfcGenericCallback callback;
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);

View file

@ -24,22 +24,6 @@ typedef enum {
FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */
} 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.
*/

View file

@ -3,11 +3,6 @@
#include <nfc/helpers/felica_crc.h>
#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) {
switch(error) {

View file

@ -55,41 +55,6 @@ typedef struct {
uint8_t request_data[2];
} 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);
/**

View 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;
}

View 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

View file

@ -224,6 +224,11 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header,
FuriString* full_extracted_fname;
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();
path_concat(op_params->work_dir, header->name, full_extracted_fname);

View file

@ -1,5 +1,5 @@
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_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,

1 entry status name type params
2 Version + 64.0 64.2
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,64.0,,
Version,+,64.2,,
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_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_poller.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_sync.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_poller.h,,
@ -1038,6 +1040,7 @@ Function,-,fdimf,float,"float, float"
Function,-,fdiml,long double,"long double, long double"
Function,-,fdopen,FILE*,"int, const char*"
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_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*"
@ -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_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t"
Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*"
Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*"
Function,+,felica_reset,void,FelicaData*
Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*"
Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t"

1 entry status name type params
2 Version + 64.0 64.2
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
138 Header + lib/nfc/protocols/emv/emv.h
139 Header + lib/nfc/protocols/emv/emv_poller.h
140 Header + lib/nfc/protocols/felica/felica.h
141 Header + lib/nfc/protocols/felica/felica_listener.h
142 Header + lib/nfc/protocols/felica/felica_poller.h
143 Header + lib/nfc/protocols/felica/felica_poller_sync.h
144 Header + lib/nfc/protocols/iso14443_3a/iso14443_3a.h
145 Header + lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h
146 Header + lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h
1040 Function - fdiml long double long double, long double
1041 Function - fdopen FILE* int, const char*
1042 Function + felica_alloc FelicaData*
1043 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*
1044 Function + felica_calculate_mac_write void mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*
1045 Function + felica_calculate_session_key void mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*
1046 Function + felica_check_mac _Bool mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*
1052 Function + felica_is_equal _Bool const FelicaData*, const FelicaData*
1053 Function + felica_load _Bool FelicaData*, FlipperFormat*, uint32_t
1054 Function + felica_poller_activate FelicaError FelicaPoller*, FelicaData*
1055 Function + felica_poller_sync_read FelicaError Nfc*, FelicaData*, const FelicaCardKey*
1056 Function + felica_reset void FelicaData*
1057 Function + felica_save _Bool const FelicaData*, FlipperFormat*
1058 Function + felica_set_uid _Bool FelicaData*, const uint8_t*, size_t

View file

@ -77,6 +77,9 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) {
if(irq & ST25R3916_IRQ_MASK_WU_A_X) {
event |= FuriHalNfcEventListenerActive;
}
if(irq & ST25R3916_IRQ_MASK_WU_F) {
event |= FuriHalNfcEventListenerActive;
}
}
if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) {
event |= FuriHalNfcEventTimerFwtExpired;

View file

@ -55,6 +55,7 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* ha
static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) {
furi_assert(handle);
st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT);
st25r3916_write_reg(
handle,
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);
// No gain reduction on AM and PM channels
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
// 10% ASK modulation
st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_10percent);
// 40% ASK modulation
st25r3916_write_reg(handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_am_mod_40percent);
// Correlator setup
st25r3916_write_reg(
@ -142,9 +143,7 @@ FuriHalNfcError furi_hal_nfc_felica_listener_tx(
FuriHalSpiBusHandle* handle,
const uint8_t* tx_data,
size_t tx_bits) {
UNUSED(handle);
UNUSED(tx_data);
UNUSED(tx_bits);
furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits);
return FuriHalNfcErrorNone;
}