mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 04:41:26 +04:00
NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks (#4254)
* SimpleArray attached to FelicaData
* tx rx done. response parsing done (in log)
* dynamic vector as buffer. rendering begin
* On screen render for directory tree
* flags in render to indicate is_public_readable
* beautify render flags
* format
* offload dynamic vector into individual files
* saving. exposed dir tree writing for double use
* save: additional formatting
* save: clean up and some additional notes
* load done
* delete unnecessary debug log
* Load: safer way to handle backward compatibility
`parsed` being true is only contingent on whether the header (device type, UID, etc) are correctly read. The detailed data can be absent if saved from previous versions.
Side effects:
1. The data format version number must not increment.
2. Newer sections of dumps must be appended in the end of the file.
* format
* handle block reading according to IC type
Old version was aimed for FeliCa Lite dumping, which doesn't apply to FeliCa standard. Thus they need to be diverged in the poller run workflow.
* read block content works. rendering begin
* Render Refactor: dir & dump view from submenu
* Render: show IC type name
* IC parsing function cleanup
* Revert "IC parsing function cleanup"
This reverts commit ee3f7bf125.
* Load: Standard dump. Fully backward compatible
* format
* sync API version
* format saved file
* delete unused variable
* clean ups
* IC type addition
* correction
* beautify attribute parsing
* correction
* Lite save: delete extra line
* correction: FeliCa link in Lite-S mode
* format
* Save: simplify printing
* update IC type parsing
* conform to api standard: const resp ptr to ptr
also slightly faster and more readable block dump loop
* disambiguate workflow type vs ic type
It was too confusing to have the ic name string telling you one thing and ic_type enum saying the other. Might as well use better naming to indicate the use case for the two things
* beautify on device render
* reject dynamic_vector, embrace m-array
* lint
* use full variable name
* partial fix: poller context's data proper init
* edit unit test dump IC code
and a small bug fix for the Lite auth workflow
* unit test felica dump PMm correction
* Fixes for static analysis warnings
---------
Co-authored-by: hedger <hedger@nanode.su>
Co-authored-by: hedger <hedger@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "felica.h"
|
||||
#include "felica_i.h"
|
||||
#include <lib/toolbox/hex.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
@@ -11,7 +12,7 @@
|
||||
#define FELICA_MANUFACTURE_ID "Manufacture id"
|
||||
#define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter"
|
||||
|
||||
static const uint32_t felica_data_format_version = 1;
|
||||
static const uint32_t felica_data_format_version = 2;
|
||||
|
||||
/** @brief This is used in felica_prepare_first_block to define which
|
||||
* type of block needs to be prepared.
|
||||
@@ -39,24 +40,71 @@ const NfcDeviceBase nfc_device_felica = {
|
||||
|
||||
FelicaData* felica_alloc(void) {
|
||||
FelicaData* data = malloc(sizeof(FelicaData));
|
||||
furi_check(data);
|
||||
|
||||
data->services = simple_array_alloc(&felica_service_array_cfg);
|
||||
data->areas = simple_array_alloc(&felica_area_array_cfg);
|
||||
data->public_blocks = simple_array_alloc(&felica_public_block_array_cfg);
|
||||
furi_check(data->services);
|
||||
furi_check(data->areas);
|
||||
furi_check(data->public_blocks);
|
||||
return data;
|
||||
}
|
||||
|
||||
void felica_free(FelicaData* data) {
|
||||
furi_check(data);
|
||||
|
||||
furi_check(data->services);
|
||||
simple_array_free(data->services);
|
||||
|
||||
furi_check(data->areas);
|
||||
simple_array_free(data->areas);
|
||||
|
||||
furi_check(data->public_blocks);
|
||||
simple_array_free(data->public_blocks);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void felica_reset(FelicaData* data) {
|
||||
furi_check(data);
|
||||
memset(data, 0, sizeof(FelicaData));
|
||||
|
||||
if(data->services) {
|
||||
simple_array_reset(data->services);
|
||||
}
|
||||
|
||||
if(data->areas) {
|
||||
simple_array_reset(data->areas);
|
||||
}
|
||||
|
||||
if(data->public_blocks) {
|
||||
simple_array_reset(data->public_blocks);
|
||||
}
|
||||
|
||||
data->blocks_read = 0;
|
||||
data->blocks_total = 0;
|
||||
data->workflow_type = FelicaUnknown;
|
||||
memset(&data->idm, 0, sizeof(data->idm));
|
||||
memset(&data->pmm, 0, sizeof(data->pmm));
|
||||
memset(&data->data, 0, sizeof(data->data));
|
||||
}
|
||||
|
||||
void felica_copy(FelicaData* data, const FelicaData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
*data = *other;
|
||||
felica_reset(data);
|
||||
|
||||
data->idm = other->idm;
|
||||
data->pmm = other->pmm;
|
||||
data->blocks_total = other->blocks_total;
|
||||
data->blocks_read = other->blocks_read;
|
||||
data->data = other->data;
|
||||
data->workflow_type = other->workflow_type;
|
||||
|
||||
simple_array_copy(data->services, other->services);
|
||||
simple_array_copy(data->areas, other->areas);
|
||||
simple_array_copy(data->public_blocks, other->public_blocks);
|
||||
}
|
||||
|
||||
bool felica_verify(FelicaData* data, const FuriString* device_type) {
|
||||
@@ -70,43 +118,164 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_check(data);
|
||||
|
||||
bool parsed = false;
|
||||
FuriString* str_key_buffer = furi_string_alloc();
|
||||
FuriString* str_data_buffer = furi_string_alloc();
|
||||
|
||||
// Header
|
||||
do {
|
||||
if(version < NFC_UNIFIED_FORMAT_VERSION) break;
|
||||
|
||||
uint32_t data_format_version = 0;
|
||||
if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1))
|
||||
break;
|
||||
if(data_format_version != felica_data_format_version) break;
|
||||
|
||||
// V1 saving function always treated everything as Felica Lite
|
||||
// So we load the blocks as if everything is Felica Lite
|
||||
|
||||
if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
uint32_t blocks_total = 0;
|
||||
uint32_t blocks_read = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
data->blocks_total = (uint8_t)blocks_total;
|
||||
data->blocks_read = (uint8_t)blocks_read;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < data->blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
parsed = false;
|
||||
break;
|
||||
}
|
||||
felica_get_workflow_type(data);
|
||||
if(data_format_version == 1) {
|
||||
data->workflow_type = FelicaLite;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
if(!parsed) {
|
||||
furi_string_free(str_key_buffer);
|
||||
furi_string_free(str_data_buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(data->workflow_type) {
|
||||
case FelicaLite:
|
||||
// Blocks data
|
||||
do {
|
||||
uint32_t blocks_total = 0;
|
||||
uint32_t blocks_read = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
data->blocks_total = (uint8_t)blocks_total;
|
||||
data->blocks_read = (uint8_t)blocks_read;
|
||||
|
||||
for(uint8_t i = 0; i < data->blocks_total; i++) {
|
||||
furi_string_printf(str_data_buffer, "Block %d", i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(str_data_buffer),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
case FelicaStandard:
|
||||
// Areas
|
||||
do {
|
||||
uint32_t area_count = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Area found", &area_count, 1)) break;
|
||||
simple_array_init(data->areas, area_count);
|
||||
|
||||
furi_string_reset(str_key_buffer);
|
||||
furi_string_reset(str_data_buffer);
|
||||
for(uint16_t i = 0; i < area_count; i++) {
|
||||
furi_string_printf(str_key_buffer, "Area %03X", i);
|
||||
if(!flipper_format_read_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) {
|
||||
break;
|
||||
}
|
||||
FelicaArea* area = simple_array_get(data->areas, i);
|
||||
if(sscanf(
|
||||
furi_string_get_cstr(str_data_buffer),
|
||||
"| Code %04hX | Services #%03hX-#%03hX |",
|
||||
&area->code,
|
||||
&area->first_idx,
|
||||
&area->last_idx) != 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
// Services
|
||||
do {
|
||||
uint32_t service_count = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Service found", &service_count, 1)) break;
|
||||
simple_array_init(data->services, service_count);
|
||||
|
||||
furi_string_reset(str_key_buffer);
|
||||
furi_string_reset(str_data_buffer);
|
||||
for(uint16_t i = 0; i < service_count; i++) {
|
||||
furi_string_printf(str_key_buffer, "Service %03X", i);
|
||||
if(!flipper_format_read_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) {
|
||||
break;
|
||||
}
|
||||
FelicaService* service = simple_array_get(data->services, i);
|
||||
|
||||
// all unread in the beginning. reserved for future block load
|
||||
if(!sscanf(
|
||||
furi_string_get_cstr(str_data_buffer), "| Code %04hX |", &service->code)) {
|
||||
break;
|
||||
}
|
||||
service->attr = service->code & 0x3F;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
// Public blocks
|
||||
do {
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
uint32_t public_block_count = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Public blocks read", &public_block_count, 1))
|
||||
break;
|
||||
simple_array_init(data->public_blocks, public_block_count);
|
||||
for(uint16_t i = 0; i < public_block_count; i++) {
|
||||
furi_string_printf(str_key_buffer, "Block %04X", i);
|
||||
if(!flipper_format_read_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer)) {
|
||||
break;
|
||||
}
|
||||
|
||||
FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i);
|
||||
if(sscanf(
|
||||
furi_string_get_cstr(str_data_buffer),
|
||||
"| Service code %04hX | Block index %02hhX |",
|
||||
&public_block->service_code,
|
||||
&public_block->block_idx) != 2) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t needle = furi_string_search_str(str_data_buffer, "Data: ");
|
||||
if(needle == FURI_STRING_FAILURE) {
|
||||
break;
|
||||
}
|
||||
needle += 6; // length of "Data: " = 6
|
||||
furi_string_mid(str_data_buffer, needle, 3 * FELICA_DATA_BLOCK_SIZE);
|
||||
furi_string_replace_all(str_data_buffer, " ", "");
|
||||
if(!hex_chars_to_uint8(
|
||||
furi_string_get_cstr(str_data_buffer), public_block->block.data)) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_reset(str_data_buffer);
|
||||
for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
|
||||
furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(str_key_buffer);
|
||||
furi_string_free(str_data_buffer);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -114,8 +283,10 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
furi_check(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
FuriString* str_data_buffer = furi_string_alloc();
|
||||
FuriString* str_key_buffer = furi_string_alloc();
|
||||
do {
|
||||
// Header
|
||||
if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break;
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1))
|
||||
@@ -126,27 +297,134 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
uint32_t blocks_total = data->blocks_total;
|
||||
uint32_t blocks_read = data->blocks_read;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
|
||||
saved = true;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
felica_get_ic_name(data, str_data_buffer);
|
||||
furi_string_replace_all(str_data_buffer, "\n", " ");
|
||||
if(!flipper_format_write_string(ff, "IC Type", str_data_buffer)) break;
|
||||
if(!flipper_format_write_empty_line(ff)) break;
|
||||
} while(false);
|
||||
|
||||
switch(data->workflow_type) {
|
||||
case FelicaLite:
|
||||
if(!flipper_format_write_comment_cstr(ff, "Felica Lite specific data")) break;
|
||||
// Blocks count
|
||||
do {
|
||||
uint32_t blocks_total = data->blocks_total;
|
||||
uint32_t blocks_read = data->blocks_read;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
|
||||
// Blocks data
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint8_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(str_key_buffer, "Block %d", i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(str_key_buffer),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
|
||||
case FelicaStandard:
|
||||
if(!flipper_format_write_comment_cstr(ff, "Felica Standard specific data")) break;
|
||||
|
||||
do {
|
||||
uint32_t area_count = simple_array_get_count(data->areas);
|
||||
uint32_t service_count = simple_array_get_count(data->services);
|
||||
// Note: The theoretical max area/service count is 2^10
|
||||
// So uint16_t is already enough for practical usage
|
||||
// The following key index print will use %03X because 12 bits are enough to cover 0-1023
|
||||
|
||||
// Area count
|
||||
if(!flipper_format_write_uint32(ff, "Area found", &area_count, 1)) break;
|
||||
|
||||
// Area data
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint16_t i = 0; i < area_count; i++) {
|
||||
FelicaArea* area = simple_array_get(data->areas, i);
|
||||
furi_string_printf(str_key_buffer, "Area %03X", i);
|
||||
furi_string_printf(
|
||||
str_data_buffer,
|
||||
"| Code %04X | Services #%03X-#%03X |",
|
||||
area->code,
|
||||
area->first_idx,
|
||||
area->last_idx);
|
||||
if(!flipper_format_write_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer))
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_empty_line(ff)) break;
|
||||
|
||||
// Service count
|
||||
if(!flipper_format_write_uint32(ff, "Service found", &service_count, 1)) break;
|
||||
|
||||
// Service data
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint16_t i = 0; i < service_count; i++) {
|
||||
FelicaService* service = simple_array_get(data->services, i);
|
||||
furi_string_printf(str_key_buffer, "Service %03X", i);
|
||||
furi_string_printf(
|
||||
str_data_buffer, "| Code %04X | Attrib. %02X ", service->code, service->attr);
|
||||
felica_service_get_attribute_string(service, str_data_buffer);
|
||||
if(!flipper_format_write_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer))
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_empty_line(ff)) break;
|
||||
|
||||
// Directory tree
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
furi_string_printf(
|
||||
str_data_buffer, "\n::: ... are public services\n||| ... are private services");
|
||||
felica_write_directory_tree(data, str_data_buffer);
|
||||
furi_string_replace_all(str_data_buffer, ":", "+");
|
||||
// We use a clearer marker in saved text files
|
||||
if(!flipper_format_write_string(ff, "Directory Tree", str_data_buffer)) break;
|
||||
} while(false);
|
||||
|
||||
// Public blocks
|
||||
do {
|
||||
uint32_t public_block_count = simple_array_get_count(data->public_blocks);
|
||||
if(!flipper_format_write_uint32(ff, "Public blocks read", &public_block_count, 1))
|
||||
break;
|
||||
furi_string_reset(str_data_buffer);
|
||||
furi_string_reset(str_key_buffer);
|
||||
for(uint16_t i = 0; i < public_block_count; i++) {
|
||||
FelicaPublicBlock* public_block = simple_array_get(data->public_blocks, i);
|
||||
furi_string_printf(str_key_buffer, "Block %04X", i);
|
||||
furi_string_printf(
|
||||
str_data_buffer,
|
||||
"| Service code %04X | Block index %02X | Data: ",
|
||||
public_block->service_code,
|
||||
public_block->block_idx);
|
||||
for(uint8_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
|
||||
furi_string_cat_printf(str_data_buffer, "%02X ", public_block->block.data[j]);
|
||||
}
|
||||
furi_string_cat_printf(str_data_buffer, "|");
|
||||
if(!flipper_format_write_string(
|
||||
ff, furi_string_get_cstr(str_key_buffer), str_data_buffer))
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
furi_string_free(str_data_buffer);
|
||||
furi_string_free(str_key_buffer);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
@@ -154,7 +432,13 @@ bool felica_is_equal(const FelicaData* data, const FelicaData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
return memcmp(data, other, sizeof(FelicaData)) == 0;
|
||||
return memcmp(data->idm.data, other->idm.data, sizeof(FelicaIDm)) == 0 &&
|
||||
memcmp(data->pmm.data, other->pmm.data, sizeof(FelicaPMm)) == 0 &&
|
||||
data->blocks_total == other->blocks_total && data->blocks_read == other->blocks_read &&
|
||||
memcmp(&data->data, &other->data, sizeof(data->data)) == 0 &&
|
||||
simple_array_is_equal(data->services, other->services) &&
|
||||
simple_array_is_equal(data->areas, other->areas) &&
|
||||
simple_array_is_equal(data->public_blocks, other->public_blocks);
|
||||
}
|
||||
|
||||
const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) {
|
||||
@@ -355,3 +639,251 @@ void felica_calculate_mac_write(
|
||||
memcpy(session_swapped + 8, session_key, 8);
|
||||
felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac);
|
||||
}
|
||||
|
||||
void felica_write_directory_tree(const FelicaData* data, FuriString* str) {
|
||||
furi_check(data);
|
||||
furi_check(str);
|
||||
|
||||
furi_string_cat_str(str, "\n");
|
||||
|
||||
uint16_t area_last_stack[8];
|
||||
uint8_t depth = 0;
|
||||
|
||||
size_t area_iter = 0;
|
||||
const size_t area_count = simple_array_get_count(data->areas);
|
||||
const size_t service_count = simple_array_get_count(data->services);
|
||||
|
||||
for(size_t svc_idx = 0; svc_idx < service_count; ++svc_idx) {
|
||||
while(area_iter < area_count) {
|
||||
const FelicaArea* next_area = simple_array_get(data->areas, area_iter);
|
||||
if(next_area->first_idx != svc_idx) break;
|
||||
|
||||
for(uint8_t i = 0; i < depth - 1; ++i)
|
||||
furi_string_cat_printf(str, "| ");
|
||||
furi_string_cat_printf(str, depth ? "|" : "");
|
||||
furi_string_cat_printf(str, "- AREA_%04X/\n", next_area->code >> 6);
|
||||
|
||||
area_last_stack[depth++] = next_area->last_idx;
|
||||
area_iter++;
|
||||
}
|
||||
|
||||
const FelicaService* service = simple_array_get(data->services, svc_idx);
|
||||
bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0;
|
||||
|
||||
for(uint8_t i = 0; i < depth - 1; ++i)
|
||||
furi_string_cat_printf(str, is_public ? ": " : "| ");
|
||||
furi_string_cat_printf(str, is_public ? ":" : "|");
|
||||
furi_string_cat_printf(str, "- serv_%04X\n", service->code);
|
||||
|
||||
if(depth && svc_idx >= area_last_stack[depth - 1]) depth--;
|
||||
}
|
||||
}
|
||||
|
||||
void felica_get_workflow_type(FelicaData* data) {
|
||||
// Reference: Proxmark3 repo
|
||||
uint8_t rom_type = data->pmm.data[0];
|
||||
uint8_t workflow_type = data->pmm.data[1];
|
||||
if(workflow_type <= 0x48) {
|
||||
// More liberal check because most of these should be treated as FeliCa Standard, regardless of mobile or not.
|
||||
data->workflow_type = FelicaStandard;
|
||||
} else {
|
||||
switch(workflow_type) {
|
||||
case 0xA2:
|
||||
data->workflow_type = FelicaStandard;
|
||||
break;
|
||||
case 0xF0:
|
||||
case 0xF1:
|
||||
case 0xF2: // 0xF2 => FeliCa Link RC-S967 in Lite-S Mode or Lite-S HT Mode
|
||||
data->workflow_type = FelicaLite;
|
||||
break;
|
||||
case 0xE1: // Felica Link
|
||||
case 0xE0: // Felica Plug
|
||||
data->workflow_type = FelicaUnknown;
|
||||
break;
|
||||
case 0xFF:
|
||||
if(rom_type == 0xFF) {
|
||||
data->workflow_type = FelicaUnknown; // Felica Link
|
||||
}
|
||||
break;
|
||||
default:
|
||||
data->workflow_type = FelicaUnknown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void felica_get_ic_name(const FelicaData* data, FuriString* ic_name) {
|
||||
// Reference: Proxmark3 repo
|
||||
uint8_t rom_type = data->pmm.data[0];
|
||||
uint8_t ic_type = data->pmm.data[1];
|
||||
|
||||
switch(ic_type) {
|
||||
// FeliCa Standard Products:
|
||||
// odd findings
|
||||
case 0x00:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S830");
|
||||
break;
|
||||
case 0x01:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S915");
|
||||
break;
|
||||
case 0x02:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S919");
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x07:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V1.0");
|
||||
break;
|
||||
case 0x08:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S952");
|
||||
break;
|
||||
case 0x09:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S953");
|
||||
break;
|
||||
case 0x0B:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S9X4,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x0C:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S954");
|
||||
break;
|
||||
case 0x0D:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S960");
|
||||
break;
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V2.0");
|
||||
break;
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V3.0");
|
||||
break;
|
||||
case 0x16:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x17:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.0");
|
||||
break;
|
||||
case 0x18:
|
||||
case 0x19:
|
||||
case 0x1A:
|
||||
case 0x1B:
|
||||
case 0x1C:
|
||||
case 0x1D:
|
||||
case 0x1E:
|
||||
case 0x1F:
|
||||
furi_string_set_str(ic_name, "FeliCa Mobile IC,\nChip V4.1");
|
||||
break;
|
||||
case 0x20:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S962");
|
||||
// RC-S962 has been extensively found in Japan Transit ICs, despite model number not ending in 4
|
||||
break;
|
||||
case 0x31:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-S104,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x32:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/1");
|
||||
break;
|
||||
case 0x33:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA00/2");
|
||||
break;
|
||||
case 0x34:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/1");
|
||||
break;
|
||||
case 0x35:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA01/2");
|
||||
break;
|
||||
case 0x36:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA04/1,\nJapan Transit IC");
|
||||
break;
|
||||
case 0x3E:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA08/1");
|
||||
break;
|
||||
case 0x43:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1");
|
||||
break;
|
||||
case 0x44:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/1");
|
||||
break;
|
||||
case 0x45:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA20/2");
|
||||
break;
|
||||
case 0x46:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2");
|
||||
break;
|
||||
case 0x47:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA24/1x1");
|
||||
break;
|
||||
case 0x48:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA21/2x1");
|
||||
break;
|
||||
case 0xA2:
|
||||
furi_string_set_str(ic_name, "FeliCa Standard RC-SA14");
|
||||
break;
|
||||
// NFC Dynamic Tag (FeliCa Plug) Products:
|
||||
case 0xE0:
|
||||
furi_string_set_str(ic_name, "FeliCa Plug RC-S926,\nNFC Dynamic Tag");
|
||||
break;
|
||||
case 0xE1:
|
||||
furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nPlug Mode");
|
||||
break;
|
||||
case 0xF0:
|
||||
furi_string_set_str(ic_name, "FeliCa Lite RC-S965");
|
||||
break;
|
||||
case 0xF1:
|
||||
furi_string_set_str(ic_name, "FeliCa Lite-S RC-S966");
|
||||
break;
|
||||
case 0xF2:
|
||||
furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nLite-S Mode or Lite-S HT Mode");
|
||||
break;
|
||||
case 0xFF:
|
||||
if(rom_type == 0xFF) { // from FeliCa Link User's Manual
|
||||
furi_string_set_str(ic_name, "FeliCa Link RC-S967,\nNFC-DEP Mode");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
furi_string_printf(
|
||||
ic_name,
|
||||
"Unknown IC %02X ROM %02X:\nPlease submit an issue on\nGitHub and help us identify.",
|
||||
ic_type,
|
||||
rom_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void felica_service_get_attribute_string(const FelicaService* service, FuriString* str) {
|
||||
furi_check(service);
|
||||
furi_check(str);
|
||||
|
||||
bool is_public = (service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) != 0;
|
||||
furi_string_cat_str(str, is_public ? "| Public " : "| Private ");
|
||||
|
||||
bool is_purse = (service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE) != 0;
|
||||
// Subfield bitwise attributes are applicable depending on is PURSE or not
|
||||
|
||||
if(is_purse) {
|
||||
furi_string_cat_str(str, "| Purse |");
|
||||
switch((service->attr & FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD) >> 1) {
|
||||
case 0:
|
||||
furi_string_cat_str(str, " Direct |");
|
||||
break;
|
||||
case 1:
|
||||
furi_string_cat_str(str, " Cashback |");
|
||||
break;
|
||||
case 2:
|
||||
furi_string_cat_str(str, " Decrement |");
|
||||
break;
|
||||
case 3:
|
||||
furi_string_cat_str(str, " Read Only |");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_str(str, " Unknown |");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
bool is_random = (service->attr & FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS) != 0;
|
||||
furi_string_cat_str(str, is_random ? "| Random |" : "| Cyclic |");
|
||||
bool is_readonly = (service->attr & FELICA_SERVICE_ATTRIBUTE_READ_ONLY) != 0;
|
||||
furi_string_cat_str(str, is_readonly ? " Read Only |" : " Read/Write |");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <toolbox/bit_buffer.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
#include <mbedtls/include/mbedtls/des.h>
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -34,6 +35,8 @@ extern "C" {
|
||||
#define FELICA_BLOCK_INDEX_STATE (0x92U)
|
||||
#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U)
|
||||
|
||||
#define FELICA_STANDARD_MAX_BLOCK_COUNT (0xFFU)
|
||||
|
||||
#define FELICA_GUARD_TIME_US (20000U)
|
||||
#define FELICA_FDT_POLL_FC (10000U)
|
||||
#define FELICA_POLL_POLL_MIN_US (1280U)
|
||||
@@ -47,6 +50,16 @@ extern "C" {
|
||||
#define FELICA_TIME_SLOT_8 (0x07U)
|
||||
#define FELICA_TIME_SLOT_16 (0x0FU)
|
||||
|
||||
#define FELICA_CMD_LIST_SERVICE_CODE 0x0A
|
||||
#define FELICA_CMD_LIST_SERVICE_CODE_RESP 0x0B
|
||||
|
||||
#define FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ (0b000001)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_READ_ONLY (0b000010)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_RANDOM_ACCESS (0b001000)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_CYCLIC (0b001100)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_PURSE (0b010000)
|
||||
#define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD (0b000110)
|
||||
|
||||
/** @brief Type of possible Felica errors */
|
||||
typedef enum {
|
||||
FelicaErrorNone,
|
||||
@@ -61,6 +74,12 @@ typedef enum {
|
||||
FelicaErrorFeatureUnsupported,
|
||||
} FelicaError;
|
||||
|
||||
typedef enum {
|
||||
FelicaUnknown,
|
||||
FelicaStandard,
|
||||
FelicaLite,
|
||||
} FelicaWorkflowType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaBlockData;
|
||||
@@ -146,6 +165,23 @@ typedef union {
|
||||
uint8_t dump[sizeof(FelicaFileSystem)];
|
||||
} FelicaFSUnion;
|
||||
|
||||
typedef struct {
|
||||
uint16_t code;
|
||||
uint8_t attr;
|
||||
} FelicaService;
|
||||
|
||||
typedef struct {
|
||||
uint16_t code;
|
||||
uint16_t first_idx;
|
||||
uint16_t last_idx;
|
||||
} FelicaArea;
|
||||
|
||||
typedef struct {
|
||||
FelicaBlock block;
|
||||
uint16_t service_code;
|
||||
uint8_t block_idx;
|
||||
} FelicaPublicBlock;
|
||||
|
||||
/** @brief Structure used to store Felica data and additional values about reading */
|
||||
typedef struct {
|
||||
FelicaIDm idm;
|
||||
@@ -153,6 +189,11 @@ typedef struct {
|
||||
uint8_t blocks_total;
|
||||
uint8_t blocks_read;
|
||||
FelicaFSUnion data;
|
||||
|
||||
SimpleArray* services;
|
||||
SimpleArray* areas;
|
||||
SimpleArray* public_blocks;
|
||||
FelicaWorkflowType workflow_type;
|
||||
} FelicaData;
|
||||
|
||||
typedef struct FURI_PACKED {
|
||||
@@ -171,6 +212,14 @@ typedef struct {
|
||||
uint8_t SF2;
|
||||
} FelicaCommandResponseHeader;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t command;
|
||||
FelicaIDm idm;
|
||||
} FelicaCommandHeaderRaw;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t service_code : 4;
|
||||
uint8_t access_mode : 3;
|
||||
@@ -194,6 +243,11 @@ typedef struct {
|
||||
uint8_t data[];
|
||||
} FelicaListenerReadCommandResponse;
|
||||
|
||||
typedef struct {
|
||||
FelicaCommandHeaderRaw header;
|
||||
uint8_t data[];
|
||||
} FelicaListServiceCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse;
|
||||
@@ -254,6 +308,15 @@ void felica_calculate_mac_write(
|
||||
const uint8_t* wcnt,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac);
|
||||
|
||||
void felica_write_directory_tree(const FelicaData* data, FuriString* str);
|
||||
|
||||
void felica_get_workflow_type(FelicaData* data);
|
||||
|
||||
void felica_get_ic_name(const FelicaData* data, FuriString* ic_name);
|
||||
|
||||
void felica_service_get_attribute_string(const FelicaService* service, FuriString* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
22
lib/nfc/protocols/felica/felica_i.c
Normal file
22
lib/nfc/protocols/felica/felica_i.c
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "felica_i.h"
|
||||
|
||||
const SimpleArrayConfig felica_service_array_cfg = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(FelicaService),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig felica_area_array_cfg = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(FelicaArea),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig felica_public_block_array_cfg = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(FelicaPublicBlock),
|
||||
};
|
||||
10
lib/nfc/protocols/felica/felica_i.h
Normal file
10
lib/nfc/protocols/felica/felica_i.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "felica.h"
|
||||
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
extern const SimpleArrayConfig felica_service_array_cfg;
|
||||
extern const SimpleArrayConfig felica_area_array_cfg;
|
||||
extern const SimpleArrayConfig felica_public_block_array_cfg;
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "felica_poller_i.h"
|
||||
#include <mlib/m-array.h>
|
||||
#include <mlib/m-core.h>
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
@@ -7,6 +9,10 @@
|
||||
|
||||
#define TAG "FelicaPoller"
|
||||
|
||||
ARRAY_DEF(felica_service_array, FelicaService, M_POD_OPLIST); // -V658
|
||||
ARRAY_DEF(felica_area_array, FelicaArea, M_POD_OPLIST); // -V658
|
||||
ARRAY_DEF(felica_public_block_array, FelicaPublicBlock, M_POD_OPLIST); // -V658
|
||||
|
||||
typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance);
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance) {
|
||||
@@ -79,15 +85,30 @@ NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) {
|
||||
FelicaError error = felica_poller_activate(instance, instance->data);
|
||||
if(error == FelicaErrorNone) {
|
||||
furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE);
|
||||
felica_get_workflow_type(instance->data);
|
||||
|
||||
instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext;
|
||||
instance->felica_event_data.auth_context = &instance->auth.context;
|
||||
|
||||
instance->callback(instance->general_event, instance->context);
|
||||
|
||||
switch(instance->data->workflow_type) {
|
||||
case FelicaStandard:
|
||||
instance->state = FelicaPollerStateTraverseStandardSystem;
|
||||
break;
|
||||
case FelicaLite:
|
||||
instance->state = FelicaPollerStateReadLiteBlocks;
|
||||
break;
|
||||
default:
|
||||
// Unimplemented
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
bool skip_auth = instance->auth.context.skip_auth;
|
||||
instance->state = skip_auth ? FelicaPollerStateReadBlocks :
|
||||
FelicaPollerStateAuthenticateInternal;
|
||||
if(!skip_auth) {
|
||||
instance->state = FelicaPollerStateAuthenticateInternal;
|
||||
}
|
||||
} else if(error != FelicaErrorTimeout) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
@@ -105,7 +126,18 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) {
|
||||
instance->data->data.fs.rc.data,
|
||||
instance->auth.session_key.data);
|
||||
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
switch(instance->data->workflow_type) {
|
||||
case FelicaStandard:
|
||||
instance->state = FelicaPollerStateTraverseStandardSystem;
|
||||
break;
|
||||
case FelicaLite:
|
||||
instance->state = FelicaPollerStateReadLiteBlocks;
|
||||
break;
|
||||
default:
|
||||
// Unimplemented
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0};
|
||||
FelicaPollerWriteCommandResponse* tx_resp;
|
||||
@@ -145,7 +177,6 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) {
|
||||
|
||||
NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Auth External");
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
uint8_t blocks[2];
|
||||
|
||||
instance->data->data.fs.state.data[0] = 1;
|
||||
@@ -183,12 +214,177 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) {
|
||||
memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE);
|
||||
instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0];
|
||||
} while(false);
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
|
||||
switch(instance->data->workflow_type) {
|
||||
case FelicaStandard:
|
||||
instance->state = FelicaPollerStateTraverseStandardSystem;
|
||||
break;
|
||||
case FelicaLite:
|
||||
instance->state = FelicaPollerStateReadLiteBlocks;
|
||||
break;
|
||||
default:
|
||||
// Unimplemented
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Blocks");
|
||||
NfcCommand felica_poller_state_handler_traverse_standard_system(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Traverse Standard System");
|
||||
|
||||
FelicaListServiceCommandResponse* response;
|
||||
|
||||
felica_service_array_t service_buffer;
|
||||
felica_service_array_init(service_buffer);
|
||||
felica_area_array_t area_buffer;
|
||||
felica_area_array_init(area_buffer);
|
||||
|
||||
for(uint16_t cursor = 0; cursor < 0xFFFF; cursor++) {
|
||||
FelicaError error = felica_poller_list_service_by_cursor(instance, cursor, &response);
|
||||
if(error != FelicaErrorNone) {
|
||||
FURI_LOG_E(TAG, "Error %d at cursor %04X", error, cursor);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t len = response->header.length;
|
||||
const uint8_t* list_service_payload = response->data;
|
||||
uint16_t code_begin = (uint16_t)(list_service_payload[0] | (list_service_payload[1] << 8));
|
||||
|
||||
if(len != 0x0C && len != 0x0E) {
|
||||
FURI_LOG_E(TAG, "Bad command resp length 0x%02X at cursor 0x%04X", len, cursor);
|
||||
break;
|
||||
}
|
||||
|
||||
if(code_begin == 0xFFFF) {
|
||||
FURI_LOG_D(TAG, "Traverse complete");
|
||||
break;
|
||||
}
|
||||
|
||||
if(len == 0x0E) {
|
||||
FelicaArea* area = felica_area_array_push_raw(area_buffer);
|
||||
memset(area, 0, sizeof *area);
|
||||
area->code = code_begin;
|
||||
area->first_idx = (uint16_t)felica_service_array_size(service_buffer);
|
||||
area->last_idx = 0;
|
||||
} else {
|
||||
FelicaService* service = felica_service_array_push_raw(service_buffer);
|
||||
memset(service, 0, sizeof *service);
|
||||
service->code = code_begin;
|
||||
service->attr = (uint8_t)(code_begin & 0x3F);
|
||||
|
||||
if(felica_area_array_size(area_buffer)) {
|
||||
FelicaArea* current_area = felica_area_array_back(area_buffer);
|
||||
current_area->last_idx = (uint16_t)(felica_service_array_size(service_buffer) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t service_num = felica_service_array_size(service_buffer);
|
||||
const size_t area_num = felica_area_array_size(area_buffer);
|
||||
|
||||
if(service_num) {
|
||||
simple_array_init(instance->data->services, (uint32_t)service_num);
|
||||
memcpy(
|
||||
simple_array_get(instance->data->services, 0),
|
||||
service_buffer->ptr,
|
||||
service_num * sizeof(FelicaService));
|
||||
} else {
|
||||
simple_array_reset(instance->data->services);
|
||||
}
|
||||
|
||||
if(area_num) {
|
||||
simple_array_init(instance->data->areas, (uint32_t)area_num);
|
||||
memcpy(
|
||||
simple_array_get(instance->data->areas, 0),
|
||||
area_buffer->ptr,
|
||||
area_num * sizeof(FelicaArea));
|
||||
} else {
|
||||
simple_array_reset(instance->data->areas);
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Services found: %lu, Areas found: %lu",
|
||||
simple_array_get_count(instance->data->services),
|
||||
simple_array_get_count(instance->data->areas));
|
||||
|
||||
felica_service_array_clear(service_buffer);
|
||||
felica_area_array_clear(area_buffer);
|
||||
|
||||
instance->state = FelicaPollerStateReadStandardBlocks;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_standard_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Standard Blocks");
|
||||
|
||||
const uint32_t service_count = simple_array_get_count(instance->data->services);
|
||||
|
||||
felica_public_block_array_t public_block_buffer;
|
||||
felica_public_block_array_init(public_block_buffer);
|
||||
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
bool have_read_anything = false;
|
||||
|
||||
for(uint32_t i = 0; i < service_count; i++) {
|
||||
const FelicaService* service = simple_array_get(instance->data->services, i);
|
||||
|
||||
if((service->attr & FELICA_SERVICE_ATTRIBUTE_UNAUTH_READ) == 0) continue;
|
||||
|
||||
uint8_t block_count = 1;
|
||||
uint8_t block_list[1] = {0};
|
||||
FelicaError error = FelicaErrorNone;
|
||||
FelicaPollerReadCommandResponse* response;
|
||||
do {
|
||||
error = felica_poller_read_blocks(
|
||||
instance, block_count, block_list, service->code, &response);
|
||||
|
||||
if(error != FelicaErrorNone) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(response->SF1 == 0 && response->SF2 == 0) {
|
||||
FelicaPublicBlock* public_block =
|
||||
felica_public_block_array_push_raw(public_block_buffer);
|
||||
memset(public_block, 0, sizeof *public_block);
|
||||
memcpy(public_block->block.data, response->data, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
public_block->service_code = service->code;
|
||||
public_block->block_idx = block_list[0];
|
||||
|
||||
have_read_anything = true;
|
||||
block_list[0]++;
|
||||
} else {
|
||||
break; // No more blocks to read in this service, ok to continue for loop
|
||||
}
|
||||
} while(block_list[0] < FELICA_STANDARD_MAX_BLOCK_COUNT);
|
||||
|
||||
if(error != FelicaErrorNone) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
instance->state = FelicaPollerStateReadFailed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(have_read_anything) {
|
||||
const size_t n = felica_public_block_array_size(public_block_buffer);
|
||||
simple_array_init(instance->data->public_blocks, (uint32_t)n);
|
||||
memcpy(
|
||||
simple_array_get(instance->data->public_blocks, 0),
|
||||
public_block_buffer->ptr,
|
||||
n * sizeof(FelicaPublicBlock));
|
||||
}
|
||||
|
||||
felica_public_block_array_clear(public_block_buffer);
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_lite_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Lite Blocks");
|
||||
|
||||
uint8_t block_count = 1;
|
||||
uint8_t block_list[4] = {0, 0, 0, 0};
|
||||
@@ -266,7 +462,10 @@ static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum]
|
||||
[FelicaPollerStateActivated] = felica_poller_state_handler_activate,
|
||||
[FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal,
|
||||
[FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external,
|
||||
[FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks,
|
||||
[FelicaPollerStateTraverseStandardSystem] =
|
||||
felica_poller_state_handler_traverse_standard_system,
|
||||
[FelicaPollerStateReadStandardBlocks] = felica_poller_state_handler_read_standard_blocks,
|
||||
[FelicaPollerStateReadLiteBlocks] = felica_poller_state_handler_read_lite_blocks,
|
||||
[FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success,
|
||||
[FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed,
|
||||
};
|
||||
|
||||
@@ -93,6 +93,7 @@ FelicaError felica_poller_polling(
|
||||
return error;
|
||||
}
|
||||
|
||||
// This is in fact a buffer preparer for a specified service. It should be have the _ex suffix. The prepare_tx_buffer_raw should have this name.
|
||||
static void felica_poller_prepare_tx_buffer(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t command,
|
||||
@@ -221,3 +222,45 @@ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void felica_poller_prepare_tx_buffer_raw(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t command,
|
||||
const uint8_t* data,
|
||||
const uint8_t data_length) {
|
||||
FelicaCommandHeaderRaw cmd = {.length = 0x00, .command = command, .idm = instance->data->idm};
|
||||
|
||||
cmd.length = sizeof(FelicaCommandHeaderRaw) + data_length;
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeaderRaw));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, data, data_length);
|
||||
}
|
||||
|
||||
FelicaError felica_poller_list_service_by_cursor(
|
||||
FelicaPoller* instance,
|
||||
uint16_t cursor,
|
||||
FelicaListServiceCommandResponse** const response_ptr) {
|
||||
furi_assert(instance);
|
||||
furi_assert(response_ptr);
|
||||
|
||||
const uint8_t data[2] = {(uint8_t)(cursor & 0xFF), (uint8_t)((cursor >> 8) & 0xFF)};
|
||||
|
||||
felica_poller_prepare_tx_buffer_raw(
|
||||
instance, FELICA_CMD_LIST_SERVICE_CODE, data, sizeof(data));
|
||||
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
FelicaError error = felica_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT);
|
||||
if(error != FelicaErrorNone) {
|
||||
FURI_LOG_E(TAG, "List service by cursor failed with error: %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer);
|
||||
if(rx_len < sizeof(FelicaCommandHeaderRaw) + 2) return FelicaErrorProtocol;
|
||||
|
||||
// error is known to be FelicaErrorNone here
|
||||
*response_ptr = (FelicaListServiceCommandResponse*)bit_buffer_get_data(instance->rx_buffer);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ typedef enum {
|
||||
FelicaPollerStateActivated,
|
||||
FelicaPollerStateAuthenticateInternal,
|
||||
FelicaPollerStateAuthenticateExternal,
|
||||
FelicaPollerStateReadBlocks,
|
||||
FelicaPollerStateTraverseStandardSystem,
|
||||
FelicaPollerStateReadStandardBlocks,
|
||||
FelicaPollerStateReadLiteBlocks,
|
||||
FelicaPollerStateReadSuccess,
|
||||
FelicaPollerStateReadFailed,
|
||||
|
||||
@@ -55,6 +57,10 @@ typedef struct {
|
||||
uint8_t request_data[2];
|
||||
} FelicaPollerPollingResponse;
|
||||
|
||||
typedef union {
|
||||
FelicaData* data;
|
||||
} FelicaPollerContextData;
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
||||
|
||||
/**
|
||||
@@ -105,6 +111,11 @@ FelicaError felica_poller_frame_exchange(
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
FelicaError felica_poller_list_service_by_cursor(
|
||||
FelicaPoller* instance,
|
||||
uint16_t cursor,
|
||||
FelicaListServiceCommandResponse** response_ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -11,8 +11,8 @@ typedef struct {
|
||||
FelicaAuthenticationContext auth_ctx;
|
||||
FuriThreadId thread_id;
|
||||
FelicaError error;
|
||||
FelicaData data;
|
||||
} Felica_PollerContext;
|
||||
FelicaPollerContextData data;
|
||||
} FelicaPollerContext;
|
||||
|
||||
NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
@@ -20,14 +20,14 @@ NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolFelica);
|
||||
|
||||
Felica_PollerContext* poller_context = context;
|
||||
FelicaPollerContext* 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);
|
||||
felica_copy(poller_context->data.data, felica_poller->data);
|
||||
} else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
|
||||
felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth;
|
||||
memcpy(
|
||||
@@ -45,7 +45,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard
|
||||
furi_check(nfc);
|
||||
furi_check(data);
|
||||
|
||||
Felica_PollerContext poller_context = {};
|
||||
FelicaPollerContext poller_context = {};
|
||||
if(card_key == NULL) {
|
||||
poller_context.auth_ctx.skip_auth = true;
|
||||
} else {
|
||||
@@ -54,6 +54,7 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard
|
||||
}
|
||||
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
poller_context.data.data = felica_alloc();
|
||||
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);
|
||||
@@ -63,8 +64,10 @@ FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCard
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(poller_context.error == FelicaErrorNone) {
|
||||
*data = poller_context.data;
|
||||
felica_copy(data, poller_context.data.data);
|
||||
}
|
||||
|
||||
felica_free(poller_context.data.data);
|
||||
|
||||
return poller_context.error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user