Merge branch 'feat/nfc-type-4-final' into mntm-dev

This commit is contained in:
WillyJL
2025-10-05 23:24:30 +02:00
242 changed files with 10348 additions and 697 deletions

View File

@@ -192,6 +192,9 @@ void lp5562_execute_ramp(
// Prepare command sequence
uint16_t program[16];
uint8_t diff = (val_end > val_start) ? (val_end - val_start) : (val_start - val_end);
if(diff == 0) { // Making division below safer
diff = 1;
}
uint16_t time_step = time * 2 / diff;
uint8_t prescaller = 0;
if(time_step > 0x3F) {

View File

@@ -3,5 +3,4 @@
#include <core/kernel.h>
#include <one_wire/one_wire_host.h>
// Function to write Dallas protocol to TM01x
bool tm01x_write_dallas(OneWireHost* host, const uint8_t* data, size_t data_size);

View File

@@ -5,6 +5,7 @@
#include <bit_lib/bit_lib.h>
#include "lfrfid_protocols.h"
#include <furi_hal_rtc.h>
#include <tools/iso_3166.h>
#define FDX_B_ENCODED_BIT_SIZE (128)
#define FDX_B_ENCODED_BYTE_SIZE (((FDX_B_ENCODED_BIT_SIZE) / 8))
@@ -287,15 +288,21 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) {
uint8_t user_info = bit_lib_get_bits(protocol->data, 55, 5);
uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3);
bool animal_flag = bit_lib_get_bit(protocol->data, 63);
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* country_full_name = furi_string_alloc();
bool country_found = iso_3166_get_full_name(storage, country_code, country_full_name);
furi_record_close(RECORD_STORAGE);
furi_string_printf(
result,
"ID: %03hu-%012llu\n"
"Country Code: %hu\n"
"Country: %s\n"
"Temperature: ",
country_code,
national_code,
country_code);
country_code,
(country_found) ? furi_string_get_cstr(country_full_name) : "Unknown");
float temperature;
if(protocol_fdx_b_get_temp(protocol->data, &temperature)) {
@@ -320,6 +327,8 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) {
reserved,
user_info,
replacement_number);
furi_string_free(country_full_name);
}
void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result) {
@@ -328,14 +337,18 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result
// 10 bit of country code
uint16_t country_code = protocol_fdx_b_get_country_code(protocol->data);
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* country_two_letter = furi_string_alloc();
bool country_found = iso_3166_get_two_letter(storage, country_code, country_two_letter);
furi_record_close(RECORD_STORAGE);
furi_string_printf(
result,
"ID: %03hu-%012llu\n"
"Country: %hu; Temp.: ",
"Country: %hu %s; Temp.: ",
country_code,
national_code,
country_code);
country_code,
(country_found) ? furi_string_get_cstr(country_two_letter) : "Unknown");
float temperature;
if(protocol_fdx_b_get_temp(protocol->data, &temperature)) {
@@ -348,6 +361,8 @@ void protocol_fdx_b_render_brief_data(ProtocolFDXB* protocol, FuriString* result
} else {
furi_string_cat(result, "---");
}
furi_string_free(country_two_letter);
}
bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) {

View File

@@ -0,0 +1,98 @@
#include "iso_3166.h"
#include <flipper_format.h>
#define RESOURCE_FILE_PATH (EXT_PATH("lfrfid/assets/iso3166.lfrfid"))
static bool lfrfid_search_data(Storage* storage, uint16_t country_code, FuriString* out_line) {
static const char* lfrfid_resources_header = "Flipper LFRFID resources";
static const uint32_t lfrfid_resources_file_version = 1;
FuriString* key = furi_string_alloc_printf("%04d", country_code);
bool found = false;
furi_string_reset(out_line);
FlipperFormat* file = flipper_format_file_alloc(storage);
FuriString* temp_str = furi_string_alloc();
do {
// Open file
if(!flipper_format_file_open_existing(file, RESOURCE_FILE_PATH)) break;
// Read file header and version
uint32_t version = 0;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, lfrfid_resources_header) ||
(version != lfrfid_resources_file_version))
break;
if(!flipper_format_read_string(file, furi_string_get_cstr(key), out_line)) break;
found = true;
} while(false);
furi_string_free(key);
furi_string_free(temp_str);
flipper_format_free(file);
if(found) {
furi_string_replace_all(out_line, " ", "");
}
return found;
}
bool iso_3166_get_two_letter(Storage* storage, uint16_t country_code, FuriString* out_two_letter) {
// We'll fetch the entire line from iso3166.lfrfid
FuriString* line = furi_string_alloc();
bool found = lfrfid_search_data(storage, country_code, line);
if(found) {
if(furi_string_size(line) < 2) {
furi_string_free(line);
FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for two-letter code");
return false; // Not enough data for a two-letter code
}
// AFAFGAfghanistan
furi_string_left(line, 2); // AF
furi_string_set(out_two_letter, line);
}
furi_string_free(line);
return found;
}
bool iso_3166_get_three_letter(
Storage* storage,
uint16_t country_code,
FuriString* out_three_letter) {
FuriString* line = furi_string_alloc();
bool found = lfrfid_search_data(storage, country_code, line);
if(found) {
if(furi_string_size(line) < 5) {
furi_string_free(line);
FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for three-letter code");
return false; // Not enough data for a three-letter code
}
// AFAFGAfghanistan
furi_string_left(line, 5); // AFAFG
furi_string_right(line, 2); // AFG
furi_string_set(out_three_letter, line);
}
furi_string_free(line);
return found;
}
bool iso_3166_get_full_name(Storage* storage, uint16_t country_code, FuriString* out_full_name) {
FuriString* line = furi_string_alloc();
bool found = lfrfid_search_data(storage, country_code, line);
if(found) {
if(furi_string_size(line) < 6) {
furi_string_free(line);
FURI_LOG_E("Lfrifd:Iso_3166", "Not enough data for full name");
return false; // Not enough data for a full name
}
// AFAFGAfghanistan
furi_string_right(line, 5); // Afghanistan
furi_string_set(out_full_name, line);
}
furi_string_free(line);
return found;
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <storage/storage.h>
#include <furi.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Look up a country's 2-letter code (ISO 3166-1 Alpha-2).
*
* @param storage Pointer to a valid Storage.
* @param country_code Numeric ISO3166 code. e.g., 4 for Afghanistan.
* @param out_two_letter A caller-allocated FuriString to fill with the 2-letter code, if found.
*/
bool iso_3166_get_two_letter(Storage* storage, uint16_t country_code, FuriString* out_two_letter);
/**
* @brief Look up a country's 3-letter code (ISO 3166-1 Alpha-3).
*
* @param storage Pointer to a valid Storage.
* @param country_code Numeric ISO3166 code.
* @param out_three_letter A caller-allocated FuriString to fill with the 3-letter code, if found.
*/
bool iso_3166_get_three_letter(
Storage* storage,
uint16_t country_code,
FuriString* out_three_letter);
/**
* @brief Look up a country's full name.
*
* @param storage Pointer to a valid Storage.
* @param country_code Numeric ISO3166 code.
* @param out_full_name A caller-allocated FuriString to fill with the country's full name.
*/
bool iso_3166_get_full_name(Storage* storage, uint16_t country_code, FuriString* out_full_name);
#ifdef __cplusplus
}
#endif

View File

@@ -26,7 +26,7 @@ typedef struct {
uint8_t dots;
} NoteBlock;
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); //-V658
struct MusicWorker {
FuriThread* thread;

View File

@@ -63,6 +63,7 @@ env.Append(
File("protocols/st25tb/st25tb_poller_sync.h"),
# Misc
File("helpers/nfc_util.h"),
File("helpers/felica_crc.h"),
File("helpers/iso14443_crc.h"),
File("helpers/iso13239_crc.h"),
File("helpers/nfc_data_generator.h"),

View File

@@ -9,6 +9,9 @@
#define NFC_MAX_BUFFER_SIZE (256)
#define NFC_FELICA_LISTENER_RESPONSE_TIME_A_FC (512 * 64)
#define NFC_FELICA_LISTENER_RESPONSE_TIME_B_FC (256 * 64)
typedef enum {
NfcStateIdle,
NfcStateRunning,
@@ -661,4 +664,20 @@ NfcError nfc_felica_listener_set_sensf_res_data(
return nfc_process_hal_error(error);
}
void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) {
furi_check(instance);
furi_hal_nfc_timer_block_tx_start(
NFC_FELICA_LISTENER_RESPONSE_TIME_A_FC +
target_time_slot * NFC_FELICA_LISTENER_RESPONSE_TIME_B_FC);
}
void nfc_felica_listener_timer_anticol_stop(Nfc* instance) {
furi_check(instance);
if(furi_hal_nfc_timer_block_tx_is_running()) {
furi_hal_nfc_timer_block_tx_stop();
}
}
#endif // FW_CFG_unit_tests

View File

@@ -380,6 +380,24 @@ NfcError nfc_felica_listener_set_sensf_res_data(
*/
NfcError nfc_iso15693_listener_tx_sof(Nfc* instance);
/**
* @brief Start the timer used for manual FeliCa collision resolution in listener mode.
*
* This blocks TX until the desired Time Slot, and should be called as soon as the listener
* determines that a collision resolution needs to be handled manually.
*
* @param[in, out] instance instance pointer to the instance to be configured.
* @param[in] target_time_slot Target Time Slot number. Should be a value within the range of 0-15 (double-inclusive).
*/
void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot);
/**
* @brief Cancel the timer used for manual FeliCa collision resolution in listener mode.
*
* @param[in, out] instance instance pointer to the instance to be configured.
*/
void nfc_felica_listener_timer_anticol_stop(Nfc* instance);
#ifdef __cplusplus
}
#endif

View File

@@ -518,4 +518,14 @@ NfcError nfc_felica_listener_set_sensf_res_data(
return NfcErrorNone;
}
void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) {
furi_check(instance);
UNUSED(target_time_slot);
}
void nfc_felica_listener_timer_anticol_stop(Nfc* instance) {
furi_check(instance);
}
#endif

View File

@@ -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,42 +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;
}
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;
}
@@ -113,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))
@@ -125,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;
}
@@ -153,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) {
@@ -354,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 |");
}
}

View File

@@ -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,11 +35,13 @@ 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)
#define FELICA_FDT_LISTEN_FC (1172)
#define FELICA_FDT_LISTEN_FC (0)
#define FELICA_SYSTEM_CODE_CODE (0xFFFFU)
#define FELICA_TIME_SLOT_1 (0x00U)
@@ -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,
@@ -58,8 +71,15 @@ typedef enum {
FelicaErrorWrongCrc,
FelicaErrorProtocol,
FelicaErrorTimeout,
FelicaErrorFeatureUnsupported,
} FelicaError;
typedef enum {
FelicaUnknown,
FelicaStandard,
FelicaLite,
} FelicaWorkflowType;
typedef struct {
uint8_t data[FELICA_DATA_BLOCK_SIZE];
} FelicaBlockData;
@@ -145,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;
@@ -152,17 +189,20 @@ typedef struct {
uint8_t blocks_total;
uint8_t blocks_read;
FelicaFSUnion data;
SimpleArray* services;
SimpleArray* areas;
SimpleArray* public_blocks;
FelicaWorkflowType workflow_type;
} FelicaData;
#pragma pack(push, 1)
typedef struct {
typedef struct FURI_PACKED {
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;
@@ -172,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;
@@ -195,6 +243,11 @@ typedef struct {
uint8_t data[];
} FelicaListenerReadCommandResponse;
typedef struct {
FelicaCommandHeaderRaw header;
uint8_t data[];
} FelicaListServiceCommandResponse;
typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse;
typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse;
@@ -255,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

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

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

View File

@@ -5,9 +5,20 @@
#include <furi_hal_nfc.h>
#define FELICA_LISTENER_MAX_BUFFER_SIZE (128)
#define FELICA_LISTENER_CMD_POLLING (0x00U)
#define FELICA_LISTENER_RESPONSE_POLLING (0x01U)
#define FELICA_LISTENER_RESPONSE_CODE_READ (0x07)
#define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09)
#define FELICA_LISTENER_REQUEST_NONE (0x00U)
#define FELICA_LISTENER_REQUEST_SYSTEM_CODE (0x01U)
#define FELICA_LISTENER_REQUEST_PERFORMANCE (0x02U)
#define FELICA_LISTENER_SYSTEM_CODE_NDEF (__builtin_bswap16(0x12FCU))
#define FELICA_LISTENER_SYSTEM_CODE_LITES (__builtin_bswap16(0x88B4U))
#define FELICA_LISTENER_PERFORMANCE_VALUE (__builtin_bswap16(0x0083U))
#define TAG "FelicaListener"
FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
@@ -151,6 +162,70 @@ static FelicaError felica_listener_process_request(
}
}
static void felica_listener_populate_polling_response_header(
FelicaListener* instance,
FelicaListenerPollingResponseHeader* resp) {
resp->idm = instance->data->idm;
resp->pmm = instance->data->pmm;
resp->response_code = FELICA_LISTENER_RESPONSE_POLLING;
}
static bool felica_listener_check_system_code(
const FelicaListenerGenericRequest* const generic_request,
uint16_t code) {
return (
generic_request->polling.system_code == code ||
generic_request->polling.system_code == (code | 0x00FFU) ||
generic_request->polling.system_code == (code | 0xFF00U));
}
static uint16_t felica_listener_get_response_system_code(
FelicaListener* instance,
const FelicaListenerGenericRequest* const generic_request) {
uint16_t resp_system_code = FELICA_SYSTEM_CODE_CODE;
if(felica_listener_check_system_code(generic_request, FELICA_LISTENER_SYSTEM_CODE_NDEF) &&
instance->data->data.fs.mc.data[FELICA_MC_SYS_OP] == 1) {
// NDEF
resp_system_code = FELICA_LISTENER_SYSTEM_CODE_NDEF;
} else if(felica_listener_check_system_code(
generic_request, FELICA_LISTENER_SYSTEM_CODE_LITES)) {
// Lite-S
resp_system_code = FELICA_LISTENER_SYSTEM_CODE_LITES;
}
return resp_system_code;
}
static FelicaError felica_listener_process_system_code(
FelicaListener* instance,
const FelicaListenerGenericRequest* const generic_request) {
FelicaError result = FelicaErrorFeatureUnsupported;
do {
uint16_t resp_system_code =
felica_listener_get_response_system_code(instance, generic_request);
if(resp_system_code == FELICA_SYSTEM_CODE_CODE) break;
FelicaListenerPollingResponse* resp = malloc(sizeof(FelicaListenerPollingResponse));
felica_listener_populate_polling_response_header(instance, &resp->header);
resp->header.length = sizeof(FelicaListenerPollingResponse);
if(generic_request->polling.request_code == FELICA_LISTENER_REQUEST_SYSTEM_CODE) {
resp->optional_request_data = resp_system_code;
} else if(generic_request->polling.request_code == FELICA_LISTENER_REQUEST_PERFORMANCE) {
resp->optional_request_data = FELICA_LISTENER_PERFORMANCE_VALUE;
} else {
resp->header.length = sizeof(FelicaListenerPollingResponseHeader);
}
bit_buffer_reset(instance->tx_buffer);
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length);
free(resp);
result = felica_listener_frame_exchange(instance, instance->tx_buffer);
} while(false);
return result;
}
NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolInvalid);
@@ -187,7 +262,23 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
break;
}
if(!felica_listener_check_idm(instance, &request->header.idm)) {
if(request->header.code == FELICA_LISTENER_CMD_POLLING) {
// Will always respond at Time Slot 0 for now.
nfc_felica_listener_timer_anticol_start(instance->nfc, 0);
if(request->polling.system_code != FELICA_SYSTEM_CODE_CODE) {
FelicaError error = felica_listener_process_system_code(instance, request);
if(error == FelicaErrorFeatureUnsupported) {
command = NfcCommandReset;
} else if(error != FelicaErrorNone) {
FURI_LOG_E(
TAG, "Error when handling Polling with System Code: %2X", error);
}
break;
} else {
FURI_LOG_E(TAG, "Hardware Polling command leaking through");
break;
}
} else if(!felica_listener_check_idm(instance, &request->header.idm)) {
FURI_LOG_E(TAG, "Wrong IDm");
break;
}

View File

@@ -7,19 +7,6 @@
#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)

View File

@@ -7,15 +7,50 @@
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U)
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U)
#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)
typedef enum {
Felica_ListenerStateIdle,
Felica_ListenerStateActivated,
} FelicaListenerState;
typedef struct FURI_PACKED {
uint8_t code;
uint16_t system_code;
uint8_t request_code;
uint8_t time_slot;
} FelicaListenerPollingHeader;
typedef struct {
uint8_t length;
uint8_t response_code;
FelicaIDm idm;
FelicaPMm pmm;
} FelicaListenerPollingResponseHeader;
typedef struct FURI_PACKED {
FelicaListenerPollingResponseHeader header;
uint16_t optional_request_data;
} FelicaListenerPollingResponse;
/** Generic Felica request same for both read and write operations. */
typedef struct {
uint8_t length;
FelicaCommandHeader header;
union {
FelicaCommandHeader header;
FelicaListenerPollingHeader polling;
};
} FelicaListenerGenericRequest;
/** Generic request but with list of requested elements. */

View File

@@ -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,
};

View File

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

View File

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

View File

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

View File

@@ -456,7 +456,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
if(instance->mfu_event.data->key_request_data.key_provided) {
instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key;
} else if(instance->mode == MfUltralightPollerModeDictAttack) {
// TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
// TODO: -nofl Can logic be rearranged to request this key
// before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller?
FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary");
// Trigger dictionary key request
instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey;
@@ -469,9 +470,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol
instance->mfu_event.data->key_request_data.key;
}
} else {
FURI_LOG_D(TAG, "No key provided, skipping auth");
instance->state = MfUltralightPollerStateReadPages;
return command;
// Fallback: use key from auth context (for sync poller compatibility)
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
}
instance->auth_context.auth_success = false;
// For debugging

View File

@@ -248,7 +248,8 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
poller_context->error = MfUltralightErrorNone;
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) {
poller_context->error = mfu_event->data->error;
poller_context->error = mf_ultralight_process_error(
mfu_poller->iso14443_3a_poller->iso14443_3a_event_data.error);
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
if(poller_context->auth_context != NULL) {

View File

@@ -650,7 +650,7 @@ static int
// evaluate flags
flags = 0U;
do {
do { //-V1044
switch(*format) {
case '0':
flags |= FLAGS_ZEROPAD;

View File

@@ -457,7 +457,7 @@ static bool
//sort by number of occurrences
bool swap = true;
while(swap) {
while(swap) { //-V1044
swap = false;
for(size_t i = 1; i < BIN_RAW_SEARCH_CLASSES; i++) {
if(classes[i].count > classes[i - 1].count) {
@@ -573,7 +573,7 @@ static bool
bit_count = 0;
if(data_markup_ind == BIN_RAW_MAX_MARKUP_COUNT) break;
ind &= 0xFFFFFFF8; //jump to the pre whole byte
ind &= 0xFFFFFFF8; //jump to the pre whole byte //-V784
}
} while(gap_ind != 0);
if((data_markup_ind != BIN_RAW_MAX_MARKUP_COUNT) && (ind != 0)) {

View File

@@ -262,6 +262,7 @@ SubGhzProtocolStatus
subghz_protocol_came_twee_remote_controller(&instance->generic);
subghz_protocol_encoder_came_twee_get_upload(instance);
instance->encoder.front = 0; // reset position before start
instance->encoder.is_running = true;
} while(false);
@@ -271,6 +272,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_came_twee_stop(void* context) {
SubGhzProtocolEncoderCameTwee* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) {

View File

@@ -169,6 +169,7 @@ SubGhzProtocolStatus
subghz_protocol_feron_check_remote_controller(&instance->generic);
subghz_protocol_encoder_feron_get_upload(instance);
instance->encoder.front = 0;
instance->encoder.is_running = true;
} while(false);
@@ -178,6 +179,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_feron_stop(void* context) {
SubGhzProtocolEncoderFeron* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_feron_yield(void* context) {

View File

@@ -80,7 +80,7 @@ void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) {
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 256;
instance->encoder.size_upload = 1024;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
@@ -183,35 +183,40 @@ static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQ
size_t index = 0;
// Send key and GAP between parcels
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
subghz_protocol_gangqi_const.te_delta);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long * 2);
for(size_t r = 0; r < 5; r++) {
// Send key and GAP between parcels
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
subghz_protocol_gangqi_const.te_delta);
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
subghz_protocol_gangqi_const.te_delta);
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_gangqi_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long);
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
subghz_protocol_gangqi_const.te_delta);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long);
}
}
}
}
@@ -262,6 +267,7 @@ SubGhzProtocolStatus
subghz_protocol_gangqi_remote_controller(&instance->generic);
subghz_protocol_encoder_gangqi_get_upload(instance);
instance->encoder.front = 0;
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
@@ -269,7 +275,7 @@ SubGhzProtocolStatus
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
@@ -285,6 +291,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_gangqi_stop(void* context) {
SubGhzProtocolEncoderGangQi* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) {
@@ -332,7 +339,7 @@ void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uin
switch(instance->decoder.parser_step) {
case GangQiDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
subghz_protocol_gangqi_const.te_delta * 3)) {
subghz_protocol_gangqi_const.te_delta * 5)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
@@ -366,19 +373,15 @@ void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uin
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) <
subghz_protocol_gangqi_const.te_delta) {
DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
subghz_protocol_gangqi_const.te_delta * 5) {
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
subghz_protocol_gangqi_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) <
subghz_protocol_gangqi_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
subghz_protocol_gangqi_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) <
subghz_protocol_gangqi_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}

View File

@@ -263,6 +263,7 @@ SubGhzProtocolStatus
subghz_protocol_hollarm_remote_controller(&instance->generic);
subghz_protocol_encoder_hollarm_get_upload(instance);
instance->encoder.front = 0;
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
@@ -270,7 +271,7 @@ SubGhzProtocolStatus
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
@@ -286,6 +287,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_hollarm_stop(void* context) {
SubGhzProtocolEncoderHollarm* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) {

View File

@@ -160,6 +160,7 @@ SubGhzProtocolStatus
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_hormann_get_upload(instance)) {
instance->encoder.front = 0; // reset position before start
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
@@ -172,6 +173,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_hormann_stop(void* context) {
SubGhzProtocolEncoderHormann* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_hormann_yield(void* context) {

View File

@@ -195,6 +195,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize(
void subghz_protocol_encoder_intertechno_v3_stop(void* context) {
SubGhzProtocolEncoderIntertechno_V3* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) {

View File

@@ -587,7 +587,7 @@ SubGhzProtocolStatus
ret = SubGhzProtocolStatusErrorParserKey;
break;
}
instance->encoder.front = 0; // reset before start
instance->encoder.is_running = true;
} while(false);
@@ -597,6 +597,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_keeloq_stop(void* context) {
SubGhzProtocolEncoderKeeloq* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) {

View File

@@ -85,7 +85,8 @@ void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) {
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = subghz_protocol_legrand_const.min_count_bit_for_found * 2 + 1;
instance->encoder.size_upload =
(subghz_protocol_legrand_const.min_count_bit_for_found * 6) * 2 + 2;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
@@ -106,31 +107,50 @@ void subghz_protocol_encoder_legrand_free(void* context) {
static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) {
furi_assert(instance);
size_t size_upload = (instance->generic.data_count_bit * 2) + 1;
if(size_upload != instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Invalid data bit count");
return false;
}
//size_t size_upload = (instance->generic.data_count_bit * 2) + 1;
//if(size_upload != instance->encoder.size_upload) {
// FURI_LOG_E(TAG, "Invalid data bit count");
// return false;
//}
size_t index = 0;
// Send sync
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 16);
// Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// send bit 1
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)instance->te * 3);
} else {
// send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)instance->te * 3);
instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te);
for(size_t r = 0; r < 5; r++) {
// Send sync
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)instance->te * 16); // 5728
// Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// send bit 1
if(i == instance->generic.data_count_bit) {
//Send first bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)instance->te * 3);
} else {
// send bit 1 regular
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)instance->te);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)instance->te * 3);
}
} else {
// send bit 0
if(i == instance->generic.data_count_bit) {
//Send first bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)instance->te);
} else {
// send bit 0 regular
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)instance->te * 3);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)instance->te);
}
}
}
}
instance->encoder.size_upload = index;
return true;
}
@@ -223,7 +243,7 @@ void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t du
switch(instance->decoder.parser_step) {
case LegrandDecoderStepReset:
if(!level && DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) <
subghz_protocol_legrand_const.te_delta * 8) {
subghz_protocol_legrand_const.te_delta * 8) { // 6000 +- 1200
instance->decoder.parser_step = LegrandDecoderStepFirstBit;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;

View File

@@ -172,6 +172,7 @@ SubGhzProtocolStatus
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_magellan_get_upload(instance)) {
instance->encoder.front = 0; // reset before start
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
@@ -184,6 +185,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_magellan_stop(void* context) {
SubGhzProtocolEncoderMagellan* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_magellan_yield(void* context) {

View File

@@ -221,6 +221,7 @@ SubGhzProtocolStatus
subghz_protocol_marantec_remote_controller(&instance->generic);
subghz_protocol_encoder_marantec_get_upload(instance);
instance->encoder.front = 0;
instance->encoder.is_running = true;
} while(false);
@@ -230,6 +231,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_marantec_stop(void* context) {
SubGhzProtocolEncoderMarantec* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_marantec_yield(void* context) {
@@ -267,6 +269,7 @@ void subghz_protocol_decoder_marantec_free(void* context) {
void subghz_protocol_decoder_marantec_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderMarantec* instance = context;
instance->decoder.parser_step = MarantecDecoderStepReset;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventReset,

View File

@@ -76,7 +76,7 @@ void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment) {
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 256;
instance->encoder.size_upload = 512;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
@@ -97,36 +97,40 @@ static void
subghz_protocol_encoder_marantec24_get_upload(SubGhzProtocolEncoderMarantec24* instance) {
furi_assert(instance);
size_t index = 0;
// Send key and GAP
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
subghz_protocol_marantec24_const.te_short);
// Send initial GAP to trigger decoder
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_marantec24_const.te_long * 9);
for(size_t r = 0; r < 4; r++) {
// Send key and GAP
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
subghz_protocol_marantec24_const.te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2);
}
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
subghz_protocol_marantec24_const.te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3);
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_marantec24_const.te_long * 9 +
subghz_protocol_marantec24_const.te_short); // 15200
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3);
}
}
}
}
@@ -163,6 +167,7 @@ SubGhzProtocolStatus
subghz_protocol_marantec24_check_remote_controller(&instance->generic);
subghz_protocol_encoder_marantec24_get_upload(instance);
instance->encoder.front = 0; // reset before start
instance->encoder.is_running = true;
} while(false);
@@ -172,6 +177,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_marantec24_stop(void* context) {
SubGhzProtocolEncoderMarantec24* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_marantec24_yield(void* context) {
@@ -233,7 +239,7 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile
switch(instance->decoder.parser_step) {
case Marantec24DecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
subghz_protocol_marantec24_const.te_delta * 4)) {
subghz_protocol_marantec24_const.te_delta * 6)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
@@ -269,7 +275,7 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
subghz_protocol_marantec24_const.te_delta * 4) {
subghz_protocol_marantec24_const.te_delta * 6) {
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) <

View File

@@ -214,6 +214,7 @@ SubGhzProtocolStatus
subghz_protocol_power_smart_remote_controller(&instance->generic);
subghz_protocol_encoder_power_smart_get_upload(instance);
instance->encoder.front = 0; // reset before start
instance->encoder.is_running = true;
} while(false);
@@ -223,6 +224,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_power_smart_stop(void* context) {
SubGhzProtocolEncoderPowerSmart* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_power_smart_yield(void* context) {

View File

@@ -78,7 +78,7 @@ void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) {
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 256;
instance->encoder.size_upload = 1768;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
@@ -128,27 +128,30 @@ static void
furi_assert(instance);
size_t index = 0;
ManchesterEncoderState enc_state;
manchester_encoder_reset(&enc_state);
ManchesterEncoderResult result;
for(size_t r = 0; r < 6; r++) {
ManchesterEncoderState enc_state;
manchester_encoder_reset(&enc_state);
ManchesterEncoderResult result;
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(!manchester_encoder_advance(
&enc_state, bit_read(instance->generic.data, i - 1), &result)) {
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(!manchester_encoder_advance(
&enc_state, bit_read(instance->generic.data, i - 1), &result)) {
instance->encoder.upload[index++] =
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
manchester_encoder_advance(
&enc_state, bit_read(instance->generic.data, i - 1), &result);
}
instance->encoder.upload[index++] =
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
manchester_encoder_advance(
&enc_state, bit_read(instance->generic.data, i - 1), &result);
}
instance->encoder.upload[index++] =
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result);
instance->encoder.upload[index] =
subghz_protocol_encoder_revers_rb2_add_duration_to_upload(
manchester_encoder_finish(&enc_state));
if(level_duration_get_level(instance->encoder.upload[index])) {
index++;
}
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320);
}
instance->encoder.upload[index] = subghz_protocol_encoder_revers_rb2_add_duration_to_upload(
manchester_encoder_finish(&enc_state));
if(level_duration_get_level(instance->encoder.upload[index])) {
index++;
}
instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320);
instance->encoder.size_upload = index;
}
@@ -181,6 +184,7 @@ SubGhzProtocolStatus
subghz_protocol_revers_rb2_remote_controller(&instance->generic);
subghz_protocol_encoder_revers_rb2_get_upload(instance);
instance->encoder.front = 0;
instance->encoder.is_running = true;
} while(false);
@@ -190,6 +194,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_revers_rb2_stop(void* context) {
SubGhzProtocolEncoderRevers_RB2* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) {
@@ -227,6 +232,8 @@ void subghz_protocol_decoder_revers_rb2_free(void* context) {
void subghz_protocol_decoder_revers_rb2_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderRevers_RB2* instance = context;
instance->decoder.parser_step = Revers_RB2DecoderStepReset;
instance->header_count = 0;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventReset,

View File

@@ -271,6 +271,7 @@ SubGhzProtocolStatus
subghz_protocol_roger_check_remote_controller(&instance->generic);
subghz_protocol_encoder_roger_get_upload(instance);
instance->encoder.front = 0;
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
@@ -290,6 +291,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_roger_stop(void* context) {
SubGhzProtocolEncoderRoger* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_roger_yield(void* context) {

View File

@@ -254,13 +254,8 @@ static void subghz_protocol_scher_khan_check_remote_controller(
instance->btn = 0;
instance->cnt = 0;
break;
case 81: //MAGIC CODE PRO / PRO2 Response ???
*protocol_name = "MAGIC CODE PRO,\n Response";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
case 82: //MAGIC CODE PRO / PRO2 Response ???
case 81: // MAGIC CODE PRO / PRO2 Response ???
case 82: // MAGIC CODE PRO / PRO2 Response ???
*protocol_name = "MAGIC CODE PRO,\n Response";
instance->serial = 0;
instance->btn = 0;

View File

@@ -580,6 +580,7 @@ SubGhzProtocolStatus
break;
}
instance->encoder.front = 0; // reset before start
instance->encoder.is_running = true;
} while(false);
@@ -589,6 +590,7 @@ SubGhzProtocolStatus
void subghz_protocol_encoder_secplus_v2_stop(void* context) {
SubGhzProtocolEncoderSecPlus_v2* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) {

View File

@@ -8,7 +8,7 @@ typedef struct {
SubGhzProtocolEncoderBase* base;
} SubGhzReceiverSlot;
ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST);
ARRAY_DEF(SubGhzReceiverSlotArray, SubGhzReceiverSlot, M_POD_OPLIST); //-V658
#define M_OPL_SubGhzReceiverSlotArray_t() ARRAY_OPLIST(SubGhzReceiverSlotArray, M_POD_OPLIST)
struct SubGhzReceiver {

View File

@@ -14,7 +14,7 @@ typedef struct {
uint16_t type;
} SubGhzKey;
ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST)
ARRAY_DEF(SubGhzKeyArray, SubGhzKey, M_POD_OPLIST) //-V658
#define M_OPL_SubGhzKeyArray_t() ARRAY_OPLIST(SubGhzKeyArray, M_POD_OPLIST)

View File

@@ -95,7 +95,7 @@ typedef struct {
size_t custom_preset_data_size;
} SubGhzSettingCustomPresetItem;
ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST)
ARRAY_DEF(SubGhzSettingCustomPresetItemArray, SubGhzSettingCustomPresetItem, M_POD_OPLIST) //-V658
#define M_OPL_SubGhzSettingCustomPresetItemArray_t() \
ARRAY_OPLIST(SubGhzSettingCustomPresetItemArray, M_POD_OPLIST)

View File

@@ -29,6 +29,7 @@ env.Append(
File("float_tools.h"),
File("value_index.h"),
File("tar/tar_archive.h"),
File("str_buffer.h"),
File("stream/stream.h"),
File("stream/file_stream.h"),
File("stream/string_stream.h"),

View File

@@ -2,6 +2,7 @@
#include "hex.h"
#include "strint.h"
#include "m-core.h"
#include <errno.h>
size_t args_get_first_word_length(FuriString* args) {
size_t ws = furi_string_search_char(args, ' ');
@@ -34,6 +35,24 @@ bool args_read_int_and_trim(FuriString* args, int* value) {
return false;
}
bool args_read_float_and_trim(FuriString* args, float* value) {
size_t cmd_length = args_get_first_word_length(args);
if(cmd_length == 0) {
return false;
}
char* end_ptr;
float temp = strtof(furi_string_get_cstr(args), &end_ptr);
if(end_ptr == furi_string_get_cstr(args)) {
return false;
}
*value = temp;
furi_string_right(args, cmd_length);
furi_string_trim(args);
return true;
}
bool args_read_string_and_trim(FuriString* args, FuriString* word) {
size_t cmd_length = args_get_first_word_length(args);
@@ -97,3 +116,38 @@ bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count) {
return result;
}
bool args_read_duration(FuriString* args, uint32_t* value, const char* default_unit) {
const char* args_cstr = furi_string_get_cstr(args);
const char* unit;
errno = 0;
double duration_ms = strtod(args_cstr, (char**)&unit);
if(errno) return false;
if(duration_ms < 0) return false;
if(unit == args_cstr) return false;
if(strcmp(unit, "") == 0) {
unit = default_unit;
if(!unit) unit = "ms";
}
uint32_t multiplier;
if(strcasecmp(unit, "ms") == 0) {
multiplier = 1;
} else if(strcasecmp(unit, "s") == 0) {
multiplier = 1000;
} else if(strcasecmp(unit, "m") == 0) {
multiplier = 60 * 1000;
} else if(strcasecmp(unit, "h") == 0) {
multiplier = 60 * 60 * 1000;
} else {
return false;
}
const uint32_t max_pre_multiplication = UINT32_MAX / multiplier;
if(duration_ms > max_pre_multiplication) return false;
*value = round(duration_ms * multiplier);
return true;
}

View File

@@ -9,17 +9,26 @@ extern "C" {
#endif
/** Extract int value and trim arguments string
*
* @param args - arguments string
* @param word first argument, output
*
* @param args - arguments string
* @param value first argument, output
* @return true - success
* @return false - arguments string does not contain int
*/
bool args_read_int_and_trim(FuriString* args, int* value);
/** Extract float value and trim arguments string
*
* @param [in, out] args arguments string
* @param [out] value first argument
* @return true - success
* @return false - arguments string does not contain float
*/
bool args_read_float_and_trim(FuriString* args, float* value);
/**
* @brief Extract first argument from arguments string and trim arguments string
*
*
* @param args arguments string
* @param word first argument, output
* @return true - success
@@ -29,7 +38,7 @@ bool args_read_string_and_trim(FuriString* args, FuriString* word);
/**
* @brief Extract the first quoted argument from the argument string and trim the argument string. If the argument is not quoted, calls args_read_string_and_trim.
*
*
* @param args arguments string
* @param word first argument, output, without quotes
* @return true - success
@@ -39,7 +48,7 @@ bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* wor
/**
* @brief Convert hex ASCII values to byte array
*
*
* @param args arguments string
* @param bytes byte array pointer, output
* @param bytes_count needed bytes count
@@ -48,11 +57,23 @@ bool args_read_probably_quoted_string_and_trim(FuriString* args, FuriString* wor
*/
bool args_read_hex_bytes(FuriString* args, uint8_t* bytes, size_t bytes_count);
/**
* @brief Parses a duration value from a given string and converts it to milliseconds
*
* @param [in] args the input string containing the duration value. The string may include units (e.g., "10s", "0.5m").
* @param [out] value pointer to store the parsed value in milliseconds
* @param [in] default_unit A default unit to be used if the input string does not contain a valid suffix.
* Supported units: `"ms"`, `"s"`, `"m"`, `"h"`
* If NULL, the function will assume milliseconds by default.
* @return `true` if the parsing and conversion succeeded, `false` otherwise.
*/
bool args_read_duration(FuriString* args, uint32_t* value, const char* default_unit);
/************************************ HELPERS ***************************************/
/**
* @brief Get length of first word from arguments string
*
*
* @param args arguments string
* @return size_t length of first word
*/
@@ -60,7 +81,7 @@ size_t args_get_first_word_length(FuriString* args);
/**
* @brief Get length of arguments string
*
*
* @param args arguments string
* @return size_t length of arguments string
*/
@@ -68,7 +89,7 @@ size_t args_length(FuriString* args);
/**
* @brief Convert ASCII hex values to byte
*
*
* @param hi_nibble ASCII hi nibble character
* @param low_nibble ASCII low nibble character
* @param byte byte pointer, output

View File

@@ -15,3 +15,18 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
}
bool cli_sleep(PipeSide* side, uint32_t duration_in_ms) {
uint32_t passed_time = 0;
bool is_interrupted = false;
do {
uint32_t left_time = duration_in_ms - passed_time;
uint32_t check_interval = left_time >= 100 ? 100 : left_time;
furi_delay_ms(check_interval);
passed_time += check_interval;
is_interrupted = cli_is_pipe_broken_or_is_etx_next_char(side);
} while(!is_interrupted && passed_time < duration_in_ms);
return !is_interrupted;
}

View File

@@ -29,14 +29,14 @@ typedef enum {
CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */
} CliCommandFlag;
/**
/**
* @brief CLI command execution callback pointer
*
*
* This callback will be called from a separate thread spawned just for your
* command. The pipe will be installed as the thread's stdio, so you can use
* `printf`, `getchar` and other standard functions to communicate with the
* user.
*
*
* @param [in] pipe Pipe that can be used to send and receive data. If
* `CliCommandFlagDontAttachStdio` was not set, you can
* also use standard C functions (printf, getc, etc.) to
@@ -64,7 +64,7 @@ typedef struct {
/**
* @brief Detects if Ctrl+C has been pressed or session has been terminated
*
*
* @param [in] side Pointer to pipe side given to the command thread
* @warning This function also assumes that the pipe is installed as the
* thread's stdio
@@ -80,6 +80,21 @@ bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side);
*/
void cli_print_usage(const char* cmd, const char* usage, const char* arg);
/**
* @brief Pause for a specified duration or until Ctrl+C is pressed or the
* session is terminated.
*
* @param [in] side Pointer to pipe side given to the command thread.
* @param [in] duration_in_ms Duration of sleep in milliseconds.
* @return `true` if the sleep completed without interruption.
* @return `false` if interrupted.
*
* @warning This function also assumes that the pipe is installed as the
* thread's stdio.
* @warning This function will consume 0 or 1 bytes from the pipe.
*/
bool cli_sleep(PipeSide* side, uint32_t duration_in_ms);
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth, app_id) \
static const CliCommandDescriptor cli_##name##_desc = { \
#name, \

View File

@@ -488,7 +488,6 @@ static int32_t cli_shell_thread(void* context) {
// ==========
// Public API
// ==========
CliShell* cli_shell_alloc(
CliShellMotd motd,
void* context,

View File

@@ -1,6 +1,6 @@
#include "cli_shell_completions.h"
ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524
ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 //-V658
#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions)
struct CliShellCompletions {

18
lib/toolbox/str_buffer.c Normal file
View File

@@ -0,0 +1,18 @@
#include "str_buffer.h"
const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str) {
char* owned = strdup(str);
buffer->n_owned_strings++;
buffer->owned_strings =
realloc(buffer->owned_strings, buffer->n_owned_strings * sizeof(const char*)); // -V701
buffer->owned_strings[buffer->n_owned_strings - 1] = owned;
return owned;
}
void str_buffer_clear_all_clones(StrBuffer* buffer) {
for(size_t i = 0; i < buffer->n_owned_strings; i++) {
free(buffer->owned_strings[i]);
}
free(buffer->owned_strings);
buffer->owned_strings = NULL;
}

47
lib/toolbox/str_buffer.h Normal file
View File

@@ -0,0 +1,47 @@
/**
* @file str_buffer.h
*
* Allows you to create an owned clone of however many strings that you need,
* then free all of them at once. Essentially the simplest possible append-only
* unindexable array of owned C-style strings.
*/
#pragma once
#include <furi.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief StrBuffer instance
*
* Place this struct directly wherever you want, it doesn't have to be `alloc`ed
* and `free`d.
*/
typedef struct {
char** owned_strings;
size_t n_owned_strings;
} StrBuffer;
/**
* @brief Makes a owned duplicate of the provided string
*
* @param[in] buffer StrBuffer instance
* @param[in] str Input C-style string
*
* @returns C-style string that contains to be valid event after `str` becomes
* invalid
*/
const char* str_buffer_make_owned_clone(StrBuffer* buffer, const char* str);
/**
* @brief Clears all owned duplicates
*
* @param[in] buffer StrBuffer instance
*/
void str_buffer_clear_all_clones(StrBuffer* buffer);
#ifdef __cplusplus
}
#endif

View File

@@ -80,7 +80,7 @@ StrintParseError strint_to_uint64_internal(
if(result > mul_limit) return StrintParseOverflowError;
result *= base;
if(result > limit - digit_value) return StrintParseOverflowError;
if(result > limit - digit_value) return StrintParseOverflowError; //-V658
result += digit_value;
read_total++;