mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-26 03:39:58 -07: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 ee3f7bf125b54b10d238b0aeb657ba15f27f93ba. * 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 |");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user