mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-31 02:23:04 -07:00
Merge branch 'dev' into zlo/tlsf-and-a-temple-of-memcorrupt
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#include "hid_service.h"
|
||||
#include "app_common.h"
|
||||
#include "app_common.h" // IWYU pragma: keep
|
||||
#include <ble/ble.h>
|
||||
#include <furi_ble/event_dispatcher.h>
|
||||
#include <furi_ble/gatt.h>
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include "lp5562_reg.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void lp5562_reset(FuriHalI2cBusHandle* handle) {
|
||||
Reg0D_Reset reg = {.value = 0xFF};
|
||||
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x0D, *(uint8_t*)®, LP5562_I2C_TIMEOUT);
|
||||
|
||||
@@ -19,17 +19,13 @@
|
||||
|
||||
#define TAG "FapAssets"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct {
|
||||
typedef struct FURI_PACKED {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t dirs_count;
|
||||
uint32_t files_count;
|
||||
} FlipperApplicationAssetsHeader;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef enum {
|
||||
AssetsSignatureResultEqual,
|
||||
AssetsSignatureResultNotEqual,
|
||||
|
||||
@@ -18,7 +18,7 @@ struct FlipperApplication {
|
||||
|
||||
/********************** Debugger access to loader state **********************/
|
||||
|
||||
LIST_DEF(FlipperApplicationList, const FlipperApplication*, M_POD_OPLIST);
|
||||
LIST_DEF(FlipperApplicationList, const FlipperApplication*, M_POD_OPLIST); // NOLINT
|
||||
|
||||
FlipperApplicationList_t flipper_application_loaded_app_list = {0};
|
||||
static bool flipper_application_loaded_app_list_initialized = false;
|
||||
@@ -277,8 +277,10 @@ static const char* preload_status_strings[] = {
|
||||
[FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error",
|
||||
[FlipperApplicationPreloadStatusInvalidFile] = "Invalid file",
|
||||
[FlipperApplicationPreloadStatusInvalidManifest] = "Invalid file manifest",
|
||||
[FlipperApplicationPreloadStatusApiTooOld] = "Update Application to use with this Firmware (ApiTooOld)",
|
||||
[FlipperApplicationPreloadStatusApiTooNew] = "Update Firmware to use with this Application (ApiTooNew)",
|
||||
[FlipperApplicationPreloadStatusApiTooOld] =
|
||||
"Update Application to use with this Firmware (ApiTooOld)",
|
||||
[FlipperApplicationPreloadStatusApiTooNew] =
|
||||
"Update Firmware to use with this Application (ApiTooNew)",
|
||||
[FlipperApplicationPreloadStatusTargetMismatch] = "Hardware target mismatch",
|
||||
};
|
||||
|
||||
@@ -286,7 +288,8 @@ static const char* load_status_strings[] = {
|
||||
[FlipperApplicationLoadStatusSuccess] = "Success",
|
||||
[FlipperApplicationLoadStatusUnspecifiedError] = "Unknown error",
|
||||
[FlipperApplicationLoadStatusNoFreeMemory] = "Out of memory",
|
||||
[FlipperApplicationLoadStatusMissingImports] = "Update Firmware to use with this Application (MissingImports)",
|
||||
[FlipperApplicationLoadStatusMissingImports] =
|
||||
"Update Firmware to use with this Application (MissingImports)",
|
||||
};
|
||||
|
||||
const char* flipper_application_preload_status_to_string(FlipperApplicationPreloadStatus status) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <m-list.h>
|
||||
#include <m-algo.h>
|
||||
|
||||
LIST_DEF(ElfApiInterfaceList, const ElfApiInterface*, M_POD_OPLIST)
|
||||
LIST_DEF(ElfApiInterfaceList, const ElfApiInterface*, M_POD_OPLIST) // NOLINT
|
||||
#define M_OPL_ElfApiInterfaceList_t() LIST_OPLIST(ElfApiInterfaceList, M_POD_OPLIST)
|
||||
|
||||
struct CompositeApiResolver {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#define TAG "PluginManager"
|
||||
|
||||
ARRAY_DEF(FlipperApplicationList, FlipperApplication*, M_PTR_OPLIST)
|
||||
ARRAY_DEF(FlipperApplicationList, FlipperApplication*, M_PTR_OPLIST) // NOLINT
|
||||
#define M_OPL_FlipperApplicationList_t() ARRAY_OPLIST(FlipperApplicationList, M_PTR_OPLIST)
|
||||
|
||||
struct PluginManager {
|
||||
|
||||
@@ -96,7 +96,7 @@ bool protocol_pac_stanley_decoder_feed(ProtocolPACStanley* protocol, bool level,
|
||||
|
||||
if(duration > PAC_STANLEY_MAX_TIME) return false;
|
||||
|
||||
uint8_t pulses = (uint8_t)round((float)duration / PAC_STANLEY_CYCLE_LENGTH);
|
||||
uint8_t pulses = (uint8_t)roundf((float)duration / PAC_STANLEY_CYCLE_LENGTH);
|
||||
|
||||
// Handle last stopbit & preamble (1 sb, 8 bit preamble)
|
||||
if(pulses >= 9 && !protocol->got_preamble) {
|
||||
|
||||
@@ -21,6 +21,7 @@ env.Append(
|
||||
File("protocols/iso14443_4b/iso14443_4b.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight.h"),
|
||||
File("protocols/mf_classic/mf_classic.h"),
|
||||
File("protocols/mf_plus/mf_plus.h"),
|
||||
File("protocols/mf_desfire/mf_desfire.h"),
|
||||
File("protocols/slix/slix.h"),
|
||||
File("protocols/st25tb/st25tb.h"),
|
||||
@@ -32,6 +33,7 @@ env.Append(
|
||||
File("protocols/iso14443_4b/iso14443_4b_poller.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
|
||||
File("protocols/mf_classic/mf_classic_poller.h"),
|
||||
File("protocols/mf_plus/mf_plus_poller.h"),
|
||||
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
||||
File("protocols/slix/slix_poller.h"),
|
||||
File("protocols/st25tb/st25tb_poller.h"),
|
||||
@@ -41,11 +43,13 @@ env.Append(
|
||||
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
|
||||
File("protocols/mf_classic/mf_classic_listener.h"),
|
||||
File("protocols/felica/felica_listener.h"),
|
||||
# Sync API
|
||||
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"),
|
||||
File("protocols/mf_classic/mf_classic_poller_sync.h"),
|
||||
File("protocols/st25tb/st25tb_poller_sync.h"),
|
||||
File("protocols/felica/felica_poller_sync.h"),
|
||||
# Misc
|
||||
File("helpers/nfc_util.h"),
|
||||
File("helpers/iso14443_crc.h"),
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#define FELICA_CRC_INIT (0x0000U)
|
||||
|
||||
uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
||||
furi_check(data);
|
||||
|
||||
uint16_t crc = FELICA_CRC_INIT;
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
@@ -24,6 +26,7 @@ uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
||||
}
|
||||
|
||||
void felica_crc_append(BitBuffer* buf) {
|
||||
furi_check(buf);
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
|
||||
@@ -32,6 +35,7 @@ void felica_crc_append(BitBuffer* buf) {
|
||||
}
|
||||
|
||||
bool felica_crc_check(const BitBuffer* buf) {
|
||||
furi_check(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
if(data_size <= FELICA_CRC_SIZE) return false;
|
||||
|
||||
@@ -45,6 +49,7 @@ bool felica_crc_check(const BitBuffer* buf) {
|
||||
}
|
||||
|
||||
void felica_crc_trim(BitBuffer* buf) {
|
||||
furi_check(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
furi_assert(data_size > FELICA_CRC_SIZE);
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <lib/nfc/nfc.h>
|
||||
#include <lib/nfc/helpers/iso14443_crc.h>
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
#include <lib/nfc/protocols/felica/felica.h>
|
||||
#include <lib/nfc/helpers/felica_crc.h>
|
||||
#include <lib/nfc/protocols/felica/felica_poller_sync.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
@@ -50,11 +53,31 @@ typedef struct {
|
||||
Iso14443_3aSelResp sel_resp[2];
|
||||
} Iso14443_3aColResData;
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t polling_cmd;
|
||||
uint16_t system_code;
|
||||
uint8_t request_code;
|
||||
uint8_t time_slot;
|
||||
} FelicaPollingRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
FelicaPMm pmm;
|
||||
} FelicaSensfResData;
|
||||
|
||||
typedef struct {
|
||||
uint16_t system_code;
|
||||
FelicaSensfResData sens_res;
|
||||
} FelicaPTMemory;
|
||||
|
||||
struct Nfc {
|
||||
NfcState state;
|
||||
|
||||
Iso14443_3aColResStatus col_res_status;
|
||||
Iso14443_3aColResData col_res_data;
|
||||
FelicaPTMemory pt_memory;
|
||||
bool software_col_res_required;
|
||||
|
||||
NfcEventCallback callback;
|
||||
@@ -243,6 +266,21 @@ static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, ui
|
||||
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||
instance->callback(event, instance->context);
|
||||
|
||||
processed = true;
|
||||
}
|
||||
} else if(rx_bits == 8 * 8) {
|
||||
FelicaPollingRequest* request = (FelicaPollingRequest*)rx_data;
|
||||
if(request->system_code == instance->pt_memory.system_code) {
|
||||
uint8_t response_size = sizeof(FelicaSensfResData) + 1;
|
||||
bit_buffer_reset(tx_buffer);
|
||||
bit_buffer_append_byte(tx_buffer, response_size);
|
||||
bit_buffer_append_bytes(
|
||||
tx_buffer, (uint8_t*)&instance->pt_memory.sens_res, sizeof(FelicaSensfResData));
|
||||
felica_crc_append(tx_buffer);
|
||||
nfc_listener_tx(instance, tx_buffer);
|
||||
instance->col_res_status = Iso14443_3aColResStatusDone;
|
||||
NfcEvent event = {.type = NfcEventTypeListenerActivated};
|
||||
instance->callback(event, instance->context);
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
@@ -470,6 +508,12 @@ NfcError nfc_felica_listener_set_sensf_res_data(
|
||||
furi_assert(idm_len == 8);
|
||||
furi_assert(pmm_len == 8);
|
||||
|
||||
instance->pt_memory.system_code = 0xFFFF;
|
||||
instance->pt_memory.sens_res.code = 0x01;
|
||||
instance->software_col_res_required = true;
|
||||
memcpy(instance->pt_memory.sens_res.idm.data, idm, idm_len);
|
||||
memcpy(instance->pt_memory.sens_res.pmm.data, pmm, pmm_len);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
|
||||
@@ -298,15 +298,33 @@ bool felica_check_mac(
|
||||
furi_check(blocks);
|
||||
furi_check(data);
|
||||
|
||||
uint8_t first_block[8];
|
||||
uint8_t mac[8];
|
||||
felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block);
|
||||
felica_calculate_mac_read(ctx, session_key, rc, blocks, block_count, data, mac);
|
||||
|
||||
uint8_t mac_offset = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
||||
uint8_t* mac_ptr = data + mac_offset;
|
||||
return !memcmp(mac, mac_ptr, 8);
|
||||
}
|
||||
|
||||
void felica_calculate_mac_read(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac) {
|
||||
furi_check(ctx);
|
||||
furi_check(session_key);
|
||||
furi_check(rc);
|
||||
furi_check(blocks);
|
||||
furi_check(data);
|
||||
furi_check(mac);
|
||||
|
||||
uint8_t first_block[8];
|
||||
felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block);
|
||||
uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1);
|
||||
felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac);
|
||||
|
||||
uint8_t* mac_ptr = data + data_size_without_mac;
|
||||
return !memcmp(mac, mac_ptr, 8);
|
||||
}
|
||||
|
||||
void felica_calculate_mac_write(
|
||||
|
||||
@@ -12,7 +12,13 @@ extern "C" {
|
||||
#define FELICA_PMM_SIZE (8U)
|
||||
#define FELICA_DATA_BLOCK_SIZE (16U)
|
||||
|
||||
#define FELICA_BLOCKS_TOTAL_COUNT (27U)
|
||||
#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U)
|
||||
#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U)
|
||||
|
||||
#define FELICA_SERVICE_RW_ACCESS (0x0009U)
|
||||
#define FELICA_SERVICE_RO_ACCESS (0x000BU)
|
||||
|
||||
#define FELICA_BLOCKS_TOTAL_COUNT (28U)
|
||||
#define FELICA_BLOCK_INDEX_REG (0x0EU)
|
||||
#define FELICA_BLOCK_INDEX_RC (0x80U)
|
||||
#define FELICA_BLOCK_INDEX_MAC (0x81U)
|
||||
@@ -54,6 +60,10 @@ typedef enum {
|
||||
FelicaErrorTimeout,
|
||||
} FelicaError;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaBlockData;
|
||||
|
||||
/** @brief Separate type for card key block. Used in authentication process */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
@@ -76,6 +86,22 @@ typedef struct {
|
||||
FelicaAuthenticationStatus auth_status; /**< Authentication status*/
|
||||
} FelicaAuthenticationContext;
|
||||
|
||||
/**
|
||||
* @brief Stucture for holding Felica session key which is calculated from rc and ck.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaSessionKey;
|
||||
|
||||
/**
|
||||
* @brief Structure used to hold authentication related fields.
|
||||
*/
|
||||
typedef struct {
|
||||
mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */
|
||||
FelicaSessionKey session_key; /**< Calculated session key. */
|
||||
FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */
|
||||
} FelicaAuthentication;
|
||||
|
||||
/** @brief Felica ID block */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_IDM_SIZE];
|
||||
@@ -128,6 +154,51 @@ typedef struct {
|
||||
FelicaFSUnion data;
|
||||
} FelicaData;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
uint8_t service_num;
|
||||
uint16_t service_code;
|
||||
uint8_t block_count;
|
||||
} FelicaCommandHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
} FelicaCommandResponseHeader;
|
||||
|
||||
typedef struct {
|
||||
uint8_t service_code : 4;
|
||||
uint8_t access_mode : 3;
|
||||
uint8_t length : 1;
|
||||
uint8_t block_number;
|
||||
} FelicaBlockListElement;
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
uint8_t block_count;
|
||||
uint8_t data[];
|
||||
} FelicaPollerReadCommandResponse;
|
||||
|
||||
typedef struct {
|
||||
FelicaCommandResponseHeader header;
|
||||
uint8_t block_count;
|
||||
uint8_t data[];
|
||||
} FelicaListenerReadCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaListenerWriteCommandResponse;
|
||||
|
||||
typedef FelicaCommandResponseHeader FelicaPollerWriteCommandResponse;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_felica;
|
||||
|
||||
FelicaData* felica_alloc(void);
|
||||
@@ -168,6 +239,15 @@ bool felica_check_mac(
|
||||
const uint8_t block_count,
|
||||
uint8_t* data);
|
||||
|
||||
void felica_calculate_mac_read(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac);
|
||||
|
||||
void felica_calculate_mac_write(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#include "felica_listener_i.h"
|
||||
|
||||
#include "nfc/protocols/nfc_listener_base.h"
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define FELICA_LISTENER_MAX_BUFFER_SIZE (64)
|
||||
#define TAG "Felica"
|
||||
#define FELICA_LISTENER_MAX_BUFFER_SIZE (128)
|
||||
#define FELICA_LISTENER_RESPONSE_CODE_READ (0x07)
|
||||
#define FELICA_LISTENER_RESPONSE_CODE_WRITE (0x09)
|
||||
|
||||
#define TAG "FelicaListener"
|
||||
|
||||
FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
||||
furi_assert(nfc);
|
||||
@@ -15,8 +20,11 @@ FelicaListener* felica_listener_alloc(Nfc* nfc, FelicaData* data) {
|
||||
instance->tx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(FELICA_LISTENER_MAX_BUFFER_SIZE);
|
||||
|
||||
mbedtls_des3_init(&instance->auth.des_context);
|
||||
nfc_set_fdt_listen_fc(instance->nfc, FELICA_FDT_LISTEN_FC);
|
||||
|
||||
memcpy(instance->mc_shadow.data, instance->data->data.fs.mc.data, FELICA_DATA_BLOCK_SIZE);
|
||||
instance->data->data.fs.state.data[0] = 0;
|
||||
nfc_config(instance->nfc, NfcModeListener, NfcTechFelica);
|
||||
nfc_felica_listener_set_sensf_res_data(
|
||||
nfc, data->idm.data, sizeof(data->idm), data->pmm.data, sizeof(data->pmm));
|
||||
@@ -49,6 +57,99 @@ const FelicaData* felica_listener_get_data(const FelicaListener* instance) {
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_command_handler_read(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerGenericRequest* const generic_request) {
|
||||
const FelicaListenerReadRequest* request = (FelicaListenerReadRequest*)generic_request;
|
||||
FURI_LOG_D(TAG, "Read cmd");
|
||||
|
||||
FelicaListenerReadCommandResponse* resp = malloc(
|
||||
sizeof(FelicaCommandResponseHeader) + 1 +
|
||||
FELICA_LISTENER_READ_BLOCK_COUNT_MAX * FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
resp->header.response_code = FELICA_LISTENER_RESPONSE_CODE_READ;
|
||||
resp->header.idm = request->base.header.idm;
|
||||
resp->header.length = sizeof(FelicaCommandResponseHeader);
|
||||
|
||||
if(felica_listener_validate_read_request_and_set_sf(instance, request, &resp->header)) {
|
||||
resp->block_count = request->base.header.block_count;
|
||||
resp->header.length++;
|
||||
} else {
|
||||
resp->block_count = 0;
|
||||
}
|
||||
|
||||
instance->mac_calc_start = 0;
|
||||
memset(instance->requested_blocks, 0, sizeof(instance->requested_blocks));
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
for(uint8_t i = 0; i < resp->block_count; i++) {
|
||||
instance->requested_blocks[i] = item->block_number;
|
||||
FelicaCommanReadBlockHandler handler =
|
||||
felica_listener_get_read_block_handler(item->block_number);
|
||||
|
||||
handler(instance, item->block_number, i, resp);
|
||||
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->header.length);
|
||||
free(resp);
|
||||
|
||||
return felica_listener_frame_exchange(instance, instance->tx_buffer);
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_command_handler_write(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerGenericRequest* const generic_request) {
|
||||
FURI_LOG_D(TAG, "Write cmd");
|
||||
|
||||
const FelicaListenerWriteRequest* request = (FelicaListenerWriteRequest*)generic_request;
|
||||
const FelicaListenerWriteBlockData* data_ptr =
|
||||
felica_listener_get_write_request_data_pointer(instance, generic_request);
|
||||
|
||||
FelicaListenerWriteCommandResponse* resp = malloc(sizeof(FelicaListenerWriteCommandResponse));
|
||||
|
||||
resp->response_code = FELICA_LISTENER_RESPONSE_CODE_WRITE;
|
||||
resp->idm = request->base.header.idm;
|
||||
resp->length = sizeof(FelicaListenerWriteCommandResponse);
|
||||
|
||||
if(felica_listener_validate_write_request_and_set_sf(instance, request, data_ptr, resp)) {
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||
FelicaCommandWriteBlockHandler handler =
|
||||
felica_listener_get_write_block_handler(item->block_number);
|
||||
|
||||
handler(instance, item->block_number, &data_ptr->blocks[i]);
|
||||
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
felica_wcnt_increment(instance->data);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)resp, resp->length);
|
||||
free(resp);
|
||||
|
||||
return felica_listener_frame_exchange(instance, instance->tx_buffer);
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_process_request(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerGenericRequest* generic_request) {
|
||||
const uint8_t cmd_code = generic_request->header.code;
|
||||
switch(cmd_code) {
|
||||
case FELICA_CMD_READ_WITHOUT_ENCRYPTION:
|
||||
return felica_listener_command_handler_read(instance, generic_request);
|
||||
case FELICA_CMD_WRITE_WITHOUT_ENCRYPTION:
|
||||
return felica_listener_command_handler_write(instance, generic_request);
|
||||
default:
|
||||
FURI_LOG_E(TAG, "FeliCa incorrect command");
|
||||
return FelicaErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
@@ -58,14 +159,44 @@ NfcCommand felica_listener_run(NfcGenericEvent event, void* context) {
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypeListenerActivated) {
|
||||
if(nfc_event->type == NfcEventTypeFieldOn) {
|
||||
FURI_LOG_D(TAG, "Field On");
|
||||
} else if(nfc_event->type == NfcEventTypeListenerActivated) {
|
||||
instance->state = Felica_ListenerStateActivated;
|
||||
FURI_LOG_D(TAG, "Activated");
|
||||
} else if(nfc_event->type == NfcEventTypeFieldOff) {
|
||||
instance->state = Felica_ListenerStateIdle;
|
||||
FURI_LOG_D(TAG, "Field Off");
|
||||
felica_listener_reset(instance);
|
||||
} else if(nfc_event->type == NfcEventTypeRxEnd) {
|
||||
FURI_LOG_D(TAG, "Rx Done");
|
||||
do {
|
||||
if(!felica_crc_check(nfc_event->data.buffer)) {
|
||||
FURI_LOG_E(TAG, "Wrong CRC");
|
||||
break;
|
||||
}
|
||||
|
||||
FelicaListenerGenericRequest* request =
|
||||
(FelicaListenerGenericRequest*)bit_buffer_get_data(nfc_event->data.buffer);
|
||||
|
||||
uint8_t size = bit_buffer_get_size_bytes(nfc_event->data.buffer) - 2;
|
||||
if((request->length != size) ||
|
||||
(!felica_listener_check_block_list_size(instance, request))) {
|
||||
FURI_LOG_E(TAG, "Wrong request length");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!felica_listener_check_idm(instance, &request->header.idm)) {
|
||||
FURI_LOG_E(TAG, "Wrong IDm");
|
||||
break;
|
||||
}
|
||||
|
||||
FelicaError error = felica_listener_process_request(instance, request);
|
||||
if(error != FelicaErrorNone) {
|
||||
FURI_LOG_E(TAG, "Processing error: %2X", error);
|
||||
}
|
||||
} while(false);
|
||||
bit_buffer_reset(nfc_event->data.buffer);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,738 @@
|
||||
#include "felica_listener_i.h"
|
||||
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
|
||||
#define FELICA_WCNT_MC2_FF_MAX_VALUE (0x00FFFFFFU)
|
||||
#define FELICA_WCNT_MC2_00_MAX_VALUE (0x00FFFE00U)
|
||||
#define FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE (0x00001027U)
|
||||
#define FELICA_WCNT_MC2_00_WARNING_END_VALUE (0x00FFFDFFU)
|
||||
|
||||
#define FELICA_MC_SP_REG_ALL_RW_BYTES_0_1 (0U)
|
||||
#define FELICA_MC_ALL_BYTE (2U)
|
||||
#define FELICA_MC_SYS_OP (3U)
|
||||
#define FELICA_MC_RF_PRM (4U)
|
||||
#define FELICA_MC_CKCKV_W_MAC_A (5U)
|
||||
#define FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 (6U)
|
||||
#define FELICA_MC_SP_REG_W_RESTR_BYTES_8_9 (8U)
|
||||
#define FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11 (10U)
|
||||
#define FELICA_MC_STATE_W_MAC_A (12U)
|
||||
#define FELICA_MC_RESERVED_13 (13U)
|
||||
#define FELICA_MC_RESERVED_14 (14U)
|
||||
#define FELICA_MC_RESERVED_15 (15U)
|
||||
|
||||
#define FELICA_MC_BYTE_GET(data, byte) (data->data.fs.mc.data[byte])
|
||||
#define FELICA_SYSTEM_BLOCK_RO_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0x00)
|
||||
#define FELICA_SYSTEM_BLOCK_RW_ACCESS(data) (FELICA_MC_BYTE_GET(data, FELICA_MC_ALL_BYTE) == 0xFF)
|
||||
|
||||
#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN ((uint8_t)sizeof(FelicaBlockListElement))
|
||||
#define FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item) \
|
||||
((item->length == 1) ? FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN : \
|
||||
(FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN + 1))
|
||||
|
||||
static uint32_t felica_wcnt_get_max_value(const FelicaData* data) {
|
||||
if(!FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && !FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) {
|
||||
furi_crash("MC[2] reserved values are forbidden");
|
||||
}
|
||||
return (FELICA_SYSTEM_BLOCK_RW_ACCESS(data)) ? FELICA_WCNT_MC2_FF_MAX_VALUE :
|
||||
FELICA_WCNT_MC2_00_MAX_VALUE;
|
||||
}
|
||||
|
||||
static bool felica_wcnt_check_warning_boundary(const FelicaData* data) {
|
||||
const uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||
return (
|
||||
FELICA_SYSTEM_BLOCK_RO_ACCESS(data) &&
|
||||
((*wcnt_ptr > FELICA_WCNT_MC2_00_WARNING_BEGIN_VALUE) &&
|
||||
(*wcnt_ptr < FELICA_WCNT_MC2_00_WARNING_END_VALUE)));
|
||||
}
|
||||
|
||||
static bool felica_wcnt_check_error_boundary(const FelicaData* data) {
|
||||
const uint32_t wcnt_max = felica_wcnt_get_max_value(data);
|
||||
const uint32_t* wcnt_ptr = (const uint32_t*)data->data.fs.wcnt.data;
|
||||
return *wcnt_ptr != wcnt_max;
|
||||
}
|
||||
|
||||
void felica_wcnt_increment(FelicaData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
const uint32_t wcnt_max = felica_wcnt_get_max_value(data);
|
||||
uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||
if(*wcnt_ptr < wcnt_max) {
|
||||
*wcnt_ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_wcnt_post_process(FelicaData* data) {
|
||||
uint32_t* wcnt_ptr = (uint32_t*)data->data.fs.wcnt.data;
|
||||
|
||||
if(FELICA_SYSTEM_BLOCK_RO_ACCESS(data) && (*wcnt_ptr > FELICA_WCNT_MC2_00_MAX_VALUE)) {
|
||||
*wcnt_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
felica_listener_block_list_item_validate_block_number(const FelicaBlockListElement* item) {
|
||||
bool valid = true;
|
||||
if(item->length == 0) {
|
||||
uint8_t D2_block_number = *(&item->block_number + 1);
|
||||
valid = D2_block_number == 0;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
static const FelicaBlockListElement* felica_listener_block_list_iterate(
|
||||
FelicaListener* instance,
|
||||
const FelicaBlockListElement* prev_item,
|
||||
const uint8_t item_size) {
|
||||
FelicaBlockListElement* next_item = NULL;
|
||||
if(instance->request_size_buf >= FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE_MIN) {
|
||||
next_item = (FelicaBlockListElement*)((uint8_t*)prev_item + item_size);
|
||||
|
||||
uint8_t next_item_size = FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(next_item);
|
||||
|
||||
if(instance->request_size_buf < next_item_size) {
|
||||
next_item = NULL;
|
||||
instance->request_size_buf = 0;
|
||||
} else {
|
||||
instance->request_size_buf -= next_item_size;
|
||||
}
|
||||
}
|
||||
|
||||
return next_item;
|
||||
}
|
||||
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_first(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerRequest* request) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request);
|
||||
|
||||
instance->request_size_buf = request->base.length - sizeof(FelicaListenerGenericRequest);
|
||||
return felica_listener_block_list_iterate(instance, request->list, 0);
|
||||
}
|
||||
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_next(
|
||||
FelicaListener* instance,
|
||||
const FelicaBlockListElement* prev_item) {
|
||||
furi_assert(instance);
|
||||
furi_assert(prev_item);
|
||||
|
||||
return felica_listener_block_list_iterate(
|
||||
instance, prev_item, FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(prev_item));
|
||||
}
|
||||
|
||||
const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer(
|
||||
const FelicaListener* const instance,
|
||||
const FelicaListenerGenericRequest* const generic_request) {
|
||||
furi_assert(instance);
|
||||
furi_assert(generic_request);
|
||||
|
||||
return (const FelicaListenerWriteBlockData*)((uint8_t*)generic_request +
|
||||
sizeof(FelicaListenerGenericRequest) +
|
||||
instance->block_list_size);
|
||||
}
|
||||
|
||||
static bool felica_listener_check_write_request_data_size(
|
||||
const FelicaListener* const instance,
|
||||
const FelicaListenerRequest* request,
|
||||
const uint8_t fact_item_cnt) {
|
||||
uint8_t possible_data_size = fact_item_cnt * FELICA_DATA_BLOCK_SIZE;
|
||||
uint8_t fact_data_size =
|
||||
request->base.length - sizeof(FelicaListenerGenericRequest) - instance->block_list_size;
|
||||
return (possible_data_size <= fact_data_size);
|
||||
}
|
||||
|
||||
static bool felica_listener_test_block_list_size_bounds(const FelicaListenerGenericRequest* req) {
|
||||
bool valid = false;
|
||||
if(req->header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) {
|
||||
valid =
|
||||
(req->header.block_count >= FELICA_LISTENER_READ_BLOCK_COUNT_MIN &&
|
||||
req->header.block_count <= FELICA_LISTENER_READ_BLOCK_COUNT_MAX);
|
||||
} else if(req->header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||
valid =
|
||||
(req->header.block_count >= FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN &&
|
||||
req->header.block_count <= FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static uint8_t felica_listener_get_block_list_item_count_size(
|
||||
FelicaListener* instance,
|
||||
FelicaListenerRequest* request) {
|
||||
uint8_t list_size = 0;
|
||||
uint8_t item_cnt = 0;
|
||||
|
||||
uint8_t max_item_cnt = (request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) ?
|
||||
FELICA_LISTENER_READ_BLOCK_COUNT_MAX :
|
||||
FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX;
|
||||
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
|
||||
while((item != NULL) && (item_cnt < max_item_cnt)) {
|
||||
item_cnt++;
|
||||
|
||||
if(request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||
if(instance->request_size_buf >= FELICA_DATA_BLOCK_SIZE) {
|
||||
instance->request_size_buf -= FELICA_DATA_BLOCK_SIZE;
|
||||
} else {
|
||||
instance->request_size_buf = 0;
|
||||
}
|
||||
}
|
||||
|
||||
list_size += FELICA_LISTENER_BLOCK_LIST_ITEM_SIZE(item);
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
|
||||
instance->block_list_size = list_size;
|
||||
return item_cnt;
|
||||
}
|
||||
|
||||
bool felica_listener_check_block_list_size(
|
||||
FelicaListener* instance,
|
||||
FelicaListenerGenericRequest* req) {
|
||||
furi_assert(instance);
|
||||
furi_assert(req);
|
||||
|
||||
FelicaListenerRequest* request = (FelicaListenerRequest*)req;
|
||||
bool valid = true;
|
||||
|
||||
uint8_t item_cnt = felica_listener_get_block_list_item_count_size(instance, request);
|
||||
valid = (item_cnt > 0);
|
||||
|
||||
if(felica_listener_test_block_list_size_bounds(req) &&
|
||||
(item_cnt < request->base.header.block_count)) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if(valid && request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) {
|
||||
valid &= felica_listener_check_write_request_data_size(instance, request, item_cnt);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request_idm);
|
||||
|
||||
const FelicaIDm* idm = &instance->data->idm;
|
||||
return memcmp(idm->data, request_idm->data, 8) == 0;
|
||||
}
|
||||
|
||||
void felica_listener_reset(FelicaListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->auth.context.auth_status.internal = false;
|
||||
instance->auth.context.auth_status.external = false;
|
||||
instance->data->data.fs.state.data[0] = 0;
|
||||
instance->rc_written = false;
|
||||
memset(instance->auth.session_key.data, 0, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
memcpy(instance->data->data.fs.mc.data, instance->mc_shadow.data, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
felica_wcnt_post_process(instance->data);
|
||||
}
|
||||
|
||||
static uint8_t felica_listener_get_block_index(uint8_t number) {
|
||||
if(number >= FELICA_BLOCK_INDEX_RC && number < FELICA_BLOCK_INDEX_WCNT) {
|
||||
return number - 0x80 + FELICA_BLOCK_INDEX_REG + 1;
|
||||
} else if(number >= FELICA_BLOCK_INDEX_WCNT && number <= FELICA_BLOCK_INDEX_STATE) {
|
||||
return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 1;
|
||||
} else if(number == FELICA_BLOCK_INDEX_CRC_CHECK) {
|
||||
return number - 0x90 + 9 + FELICA_BLOCK_INDEX_REG + 2;
|
||||
}
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
static bool felica_block_exists(uint8_t number) {
|
||||
return !(
|
||||
(number > FELICA_BLOCK_INDEX_REG && number < FELICA_BLOCK_INDEX_RC) ||
|
||||
(number > FELICA_BLOCK_INDEX_MC && number < FELICA_BLOCK_INDEX_WCNT) ||
|
||||
(number > FELICA_BLOCK_INDEX_STATE && number < FELICA_BLOCK_INDEX_CRC_CHECK) ||
|
||||
(number > FELICA_BLOCK_INDEX_CRC_CHECK));
|
||||
}
|
||||
|
||||
static bool
|
||||
felica_get_mc_bit(const FelicaListener* instance, uint8_t byte_index, uint8_t bit_number) {
|
||||
uint8_t* mc = instance->data->data.fs.mc.data;
|
||||
|
||||
uint16_t flags = *((uint16_t*)&mc[byte_index]);
|
||||
bool bit = (flags & (1 << bit_number)) != 0;
|
||||
return bit;
|
||||
}
|
||||
|
||||
static bool felica_block_requires_auth(
|
||||
const FelicaListener* instance,
|
||||
uint8_t command,
|
||||
uint8_t block_number) {
|
||||
bool need_auth = false;
|
||||
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||
uint8_t mc_flag_index = command;
|
||||
need_auth = felica_get_mc_bit(instance, mc_flag_index, block_number);
|
||||
}
|
||||
return need_auth;
|
||||
}
|
||||
|
||||
static bool felica_block_is_readonly(const FelicaListener* instance, uint8_t block_number) {
|
||||
bool readonly = false;
|
||||
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||
readonly = !felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, block_number);
|
||||
} else if(
|
||||
((block_number == FELICA_BLOCK_INDEX_ID) || (block_number == FELICA_BLOCK_INDEX_SER_C)) ||
|
||||
(block_number >= FELICA_BLOCK_INDEX_CKV && block_number <= FELICA_BLOCK_INDEX_CK)) {
|
||||
const FelicaData* data = instance->data;
|
||||
readonly = FELICA_SYSTEM_BLOCK_RO_ACCESS(data);
|
||||
} else if(
|
||||
(block_number == FELICA_BLOCK_INDEX_MAC) || (block_number == FELICA_BLOCK_INDEX_WCNT) ||
|
||||
(block_number == FELICA_BLOCK_INDEX_D_ID) ||
|
||||
(block_number == FELICA_BLOCK_INDEX_CRC_CHECK)) {
|
||||
readonly = true;
|
||||
}
|
||||
return readonly;
|
||||
}
|
||||
|
||||
static bool felica_block_requires_mac(const FelicaListener* instance, uint8_t block_number) {
|
||||
bool need_mac = false;
|
||||
if(block_number <= FELICA_BLOCK_INDEX_REG) {
|
||||
need_mac = felica_get_mc_bit(instance, FELICA_MC_SP_REG_W_MAC_A_BYTES_10_11, block_number);
|
||||
} else if(block_number == FELICA_BLOCK_INDEX_CK || block_number == FELICA_BLOCK_INDEX_CKV) {
|
||||
need_mac = felica_get_mc_bit(instance, FELICA_MC_CKCKV_W_MAC_A, 0);
|
||||
} else if(block_number == FELICA_BLOCK_INDEX_STATE) {
|
||||
need_mac = felica_get_mc_bit(instance, FELICA_MC_STATE_W_MAC_A, 0);
|
||||
}
|
||||
return need_mac;
|
||||
}
|
||||
|
||||
static void felica_handler_read_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response) {
|
||||
uint8_t num = felica_listener_get_block_index(block_number);
|
||||
memcpy(
|
||||
&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE],
|
||||
&instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2],
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
response->header.length += FELICA_DATA_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
static void felica_handler_read_all_zeros(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response) {
|
||||
UNUSED(instance);
|
||||
UNUSED(block_number);
|
||||
|
||||
memset(&response->data[resp_data_index * FELICA_DATA_BLOCK_SIZE], 0, FELICA_DATA_BLOCK_SIZE);
|
||||
response->header.length += FELICA_DATA_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
static void felica_handler_read_mac_a_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response) {
|
||||
if((resp_data_index != response->block_count - 1) || (resp_data_index == 0)) {
|
||||
felica_handler_read_all_zeros(instance, block_number, resp_data_index, response);
|
||||
instance->mac_calc_start = resp_data_index + 1;
|
||||
} else {
|
||||
felica_calculate_mac_read(
|
||||
&instance->auth.des_context,
|
||||
instance->auth.session_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
&instance->requested_blocks[instance->mac_calc_start],
|
||||
response->block_count - instance->mac_calc_start,
|
||||
&response->data[instance->mac_calc_start * FELICA_DATA_BLOCK_SIZE],
|
||||
instance->data->data.fs.mac_a.data);
|
||||
felica_handler_read_block(instance, block_number, resp_data_index, response);
|
||||
}
|
||||
}
|
||||
|
||||
FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number) {
|
||||
FelicaCommanReadBlockHandler handler = felica_handler_read_block;
|
||||
|
||||
if(block_number == FELICA_BLOCK_INDEX_RC || block_number == FELICA_BLOCK_INDEX_CK) {
|
||||
handler = felica_handler_read_all_zeros;
|
||||
} else if(block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||
handler = felica_handler_read_mac_a_block;
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
static bool felica_validate_read_block_list(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerReadRequest* const request,
|
||||
FelicaCommandResponseHeader* response) {
|
||||
uint8_t mac_a_pos = 0;
|
||||
bool mac_a_present = false, mac_present = false;
|
||||
|
||||
const FelicaBlockListElement* item =
|
||||
felica_listener_block_list_item_get_first(instance, request);
|
||||
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||
if(item->service_code != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA3;
|
||||
return false;
|
||||
} else if(item->access_mode != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA7;
|
||||
return false;
|
||||
} else if(
|
||||
!felica_listener_block_list_item_validate_block_number(item) ||
|
||||
!felica_block_exists(item->block_number) ||
|
||||
(felica_block_is_readonly(instance, item->block_number) &&
|
||||
request->base.header.service_code != FELICA_SERVICE_RO_ACCESS)) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA8;
|
||||
return false;
|
||||
} else if(item->block_number == FELICA_BLOCK_INDEX_MAC) {
|
||||
mac_present = true;
|
||||
} else if(item->block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||
if(!instance->rc_written) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xB2;
|
||||
return false;
|
||||
}
|
||||
if(!mac_a_present) {
|
||||
mac_a_present = true;
|
||||
mac_a_pos = i;
|
||||
}
|
||||
} else if(
|
||||
felica_block_requires_auth(instance, request->base.header.code, item->block_number) &&
|
||||
!instance->auth.context.auth_status.external) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xB1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mac_a_present && mac_present) {
|
||||
response->SF1 = (1 << mac_a_pos);
|
||||
response->SF2 = 0xB0;
|
||||
return false;
|
||||
}
|
||||
|
||||
item = felica_listener_block_list_item_get_next(instance, item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool felica_listener_validate_read_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerReadRequest* const request,
|
||||
FelicaCommandResponseHeader* resp_header) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request);
|
||||
furi_assert(resp_header);
|
||||
|
||||
bool valid = false;
|
||||
do {
|
||||
if(request->base.header.service_num != 0x01) {
|
||||
resp_header->SF1 = 0xFF;
|
||||
resp_header->SF2 = 0xA1;
|
||||
break;
|
||||
}
|
||||
if((request->base.header.code == FELICA_CMD_READ_WITHOUT_ENCRYPTION) &&
|
||||
(request->base.header.block_count < FELICA_LISTENER_READ_BLOCK_COUNT_MIN ||
|
||||
request->base.header.block_count > FELICA_LISTENER_READ_BLOCK_COUNT_MAX)) {
|
||||
resp_header->SF1 = 0xFF;
|
||||
resp_header->SF2 = 0xA2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(request->base.header.service_code != FELICA_SERVICE_RO_ACCESS &&
|
||||
request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) {
|
||||
resp_header->SF1 = 0x01;
|
||||
resp_header->SF2 = 0xA6;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!felica_validate_read_block_list(instance, request, resp_header)) break;
|
||||
|
||||
resp_header->SF1 = 0x00;
|
||||
resp_header->SF2 = 0x00;
|
||||
valid = true;
|
||||
} while(false);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool felica_validate_write_block_list(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerWriteRequest* const request,
|
||||
const FelicaListenerWriteBlockData* const data,
|
||||
FelicaListenerWriteCommandResponse* response) {
|
||||
const FelicaBlockListElement* items[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX] = {};
|
||||
items[0] = felica_listener_block_list_item_get_first(instance, request);
|
||||
items[1] = felica_listener_block_list_item_get_next(instance, items[0]);
|
||||
|
||||
bool write_with_mac = false;
|
||||
if(request->base.header.block_count == FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX &&
|
||||
items[1]->block_number == FELICA_BLOCK_INDEX_MAC_A) {
|
||||
write_with_mac = true;
|
||||
} else if(
|
||||
request->base.header.block_count < FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN ||
|
||||
request->base.header.block_count > FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX) {
|
||||
response->SF1 = 0x02;
|
||||
response->SF2 = 0xB2;
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < request->base.header.block_count; i++) {
|
||||
const FelicaBlockListElement* item = items[i];
|
||||
if(!felica_listener_block_list_item_validate_block_number(item) ||
|
||||
!felica_block_exists(item->block_number) ||
|
||||
((i == 1) && (item->block_number != FELICA_BLOCK_INDEX_MAC_A))) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA8;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(felica_block_is_readonly(instance, item->block_number) ||
|
||||
(felica_block_requires_mac(instance, item->block_number) && !write_with_mac) ||
|
||||
((i == 0) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A))) {
|
||||
response->SF1 = 0x01;
|
||||
response->SF2 = 0xA8;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(item->service_code != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA3;
|
||||
return false;
|
||||
} else if(item->access_mode != 0) {
|
||||
response->SF1 = (1 << i);
|
||||
response->SF2 = 0xA7;
|
||||
return false;
|
||||
} else if((i == 1) && (item->block_number == FELICA_BLOCK_INDEX_MAC_A)) {
|
||||
uint8_t calculated_mac[8];
|
||||
felica_calculate_mac_write(
|
||||
&instance->auth.des_context,
|
||||
instance->auth.session_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
data->blocks[1].data + 8,
|
||||
data->blocks[0].data,
|
||||
calculated_mac);
|
||||
|
||||
if(!instance->rc_written || (memcmp(calculated_mac, data->blocks[1].data, 8) != 0) ||
|
||||
!felica_wcnt_check_error_boundary(instance->data)) {
|
||||
response->SF1 = 0x02;
|
||||
response->SF2 = 0xB2;
|
||||
return false;
|
||||
}
|
||||
} else if(
|
||||
felica_block_requires_auth(instance, request->base.header.code, item->block_number) &&
|
||||
!instance->auth.context.auth_status.external) {
|
||||
response->SF1 = 0x01;
|
||||
response->SF2 = 0xB1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool felica_listener_validate_write_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerWriteRequest* const request,
|
||||
const FelicaListenerWriteBlockData* const data,
|
||||
FelicaListenerWriteCommandResponse* response) {
|
||||
furi_assert(instance);
|
||||
furi_assert(request);
|
||||
furi_assert(data);
|
||||
furi_assert(response);
|
||||
|
||||
bool valid = false;
|
||||
do {
|
||||
if(request->base.header.service_num != 0x01) {
|
||||
response->SF1 = 0xFF;
|
||||
response->SF2 = 0xA1;
|
||||
break;
|
||||
}
|
||||
|
||||
if((request->base.header.code == FELICA_CMD_WRITE_WITHOUT_ENCRYPTION) &&
|
||||
(request->base.header.block_count < 0x01 || request->base.header.block_count > 0x02)) {
|
||||
response->SF1 = 0xFF;
|
||||
response->SF2 = 0xA2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(request->base.header.service_code != FELICA_SERVICE_RW_ACCESS) {
|
||||
response->SF1 = 0x01;
|
||||
response->SF2 = 0xA6;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!felica_validate_write_block_list(instance, request, data, response)) break;
|
||||
|
||||
if(felica_wcnt_check_warning_boundary(instance->data)) {
|
||||
response->SF1 = 0xFF;
|
||||
response->SF2 = 0x71;
|
||||
} else {
|
||||
response->SF1 = 0x00;
|
||||
response->SF2 = 0x00;
|
||||
}
|
||||
valid = true;
|
||||
} while(false);
|
||||
return valid;
|
||||
}
|
||||
|
||||
static void felica_handler_write_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
uint8_t num = felica_listener_get_block_index(block_number);
|
||||
|
||||
memcpy(
|
||||
&instance->data->data.dump[num * (FELICA_DATA_BLOCK_SIZE + 2) + 2],
|
||||
data_block->data,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
static void felica_handler_write_rc_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
felica_handler_write_block(instance, block_number, data_block);
|
||||
|
||||
felica_calculate_session_key(
|
||||
&instance->auth.des_context,
|
||||
instance->data->data.fs.ck.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
instance->auth.session_key.data);
|
||||
instance->rc_written = true;
|
||||
}
|
||||
|
||||
static void felica_handler_write_reg_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
UNUSED(block_number);
|
||||
|
||||
typedef struct {
|
||||
uint32_t A;
|
||||
uint32_t B;
|
||||
uint64_t C;
|
||||
} FELICA_REG_BLOCK;
|
||||
|
||||
FELICA_REG_BLOCK* Reg = (FELICA_REG_BLOCK*)instance->data->data.fs.reg.data;
|
||||
FELICA_REG_BLOCK* RegNew = (FELICA_REG_BLOCK*)data_block->data;
|
||||
|
||||
if(Reg->A >= RegNew->A) {
|
||||
Reg->A = RegNew->A;
|
||||
}
|
||||
|
||||
if(Reg->B >= RegNew->B) {
|
||||
Reg->B = RegNew->B;
|
||||
}
|
||||
|
||||
if((Reg->A >= RegNew->A) && (Reg->B >= RegNew->B)) {
|
||||
Reg->C = RegNew->C;
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_handler_write_mc_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
UNUSED(block_number);
|
||||
|
||||
bool mc_bits_permission = felica_get_mc_bit(instance, FELICA_MC_SP_REG_ALL_RW_BYTES_0_1, 15);
|
||||
const FelicaData* data = instance->data;
|
||||
bool mc_system_block_permission = FELICA_SYSTEM_BLOCK_RW_ACCESS(data);
|
||||
for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
|
||||
if((i <= 1) && mc_bits_permission) {
|
||||
instance->mc_shadow.data[i] &= data_block->data[i];
|
||||
} else if(
|
||||
(i >= FELICA_MC_ALL_BYTE && i <= FELICA_MC_CKCKV_W_MAC_A) &&
|
||||
(mc_system_block_permission)) {
|
||||
instance->mc_shadow.data[i] = data_block->data[i];
|
||||
} else if(
|
||||
(i >= FELICA_MC_SP_REG_R_RESTR_BYTES_6_7 && i <= FELICA_MC_STATE_W_MAC_A) &&
|
||||
(mc_bits_permission)) {
|
||||
instance->mc_shadow.data[i] |= data_block->data[i];
|
||||
} else if(i >= FELICA_MC_RESERVED_13) {
|
||||
instance->mc_shadow.data[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void felica_handler_write_state_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
felica_handler_write_block(instance, block_number, data_block);
|
||||
bool state = instance->data->data.fs.state.data[0] == 0x01;
|
||||
instance->auth.context.auth_status.external = state;
|
||||
instance->auth.context.auth_status.internal = state;
|
||||
}
|
||||
|
||||
static void felica_handler_write_id_block(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block) {
|
||||
UNUSED(block_number);
|
||||
memcpy(&instance->data->data.fs.id.data[8], &data_block->data[8], 8);
|
||||
}
|
||||
|
||||
FelicaCommandWriteBlockHandler
|
||||
felica_listener_get_write_block_handler(const uint8_t block_number) {
|
||||
FelicaCommandWriteBlockHandler handler = felica_handler_write_block;
|
||||
switch(block_number) {
|
||||
case FELICA_BLOCK_INDEX_RC:
|
||||
handler = felica_handler_write_rc_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_REG:
|
||||
handler = felica_handler_write_reg_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_MC:
|
||||
handler = felica_handler_write_mc_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_STATE:
|
||||
handler = felica_handler_write_state_block;
|
||||
break;
|
||||
case FELICA_BLOCK_INDEX_ID:
|
||||
handler = felica_handler_write_id_block;
|
||||
break;
|
||||
default:
|
||||
handler = felica_handler_write_block;
|
||||
break;
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
static FelicaError felica_listener_process_error(NfcError error) {
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
return FelicaErrorNone;
|
||||
case NfcErrorTimeout:
|
||||
return FelicaErrorTimeout;
|
||||
default:
|
||||
return FelicaErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
FelicaError
|
||||
felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer);
|
||||
furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE);
|
||||
|
||||
felica_crc_append(instance->tx_buffer);
|
||||
|
||||
FelicaError ret = FelicaErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = felica_listener_process_error(error);
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -2,15 +2,72 @@
|
||||
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
|
||||
#define FELICA_LISTENER_READ_BLOCK_COUNT_MAX (4U)
|
||||
#define FELICA_LISTENER_READ_BLOCK_COUNT_MIN (1U)
|
||||
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX (2U)
|
||||
#define FELICA_LISTENER_WRITE_BLOCK_COUNT_MIN (1U)
|
||||
|
||||
typedef enum {
|
||||
Felica_ListenerStateIdle,
|
||||
Felica_ListenerStateActivated,
|
||||
} FelicaListenerState;
|
||||
|
||||
/** Generic Felica request same for both read and write operations. */
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
FelicaCommandHeader header;
|
||||
} FelicaListenerGenericRequest;
|
||||
|
||||
/** Generic request but with list of requested elements. */
|
||||
typedef struct {
|
||||
FelicaListenerGenericRequest base;
|
||||
FelicaBlockListElement list[];
|
||||
} FelicaListenerRequest;
|
||||
|
||||
typedef FelicaListenerRequest FelicaListenerReadRequest;
|
||||
typedef FelicaListenerRequest FelicaListenerWriteRequest;
|
||||
|
||||
/** Struct for write request data.
|
||||
*
|
||||
* All data are placed right after @ref FelicaListenerRequest data.
|
||||
*/
|
||||
typedef struct {
|
||||
FelicaBlockData blocks[FELICA_LISTENER_WRITE_BLOCK_COUNT_MAX];
|
||||
} FelicaListenerWriteBlockData;
|
||||
|
||||
/** Write command handler signature.
|
||||
*
|
||||
* Depending on requested block write behaviour can be different, therefore
|
||||
* different handlers are used.
|
||||
*/
|
||||
typedef void (*FelicaCommandWriteBlockHandler)(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const FelicaBlockData* data_block);
|
||||
|
||||
/** Read command handler signature.
|
||||
*
|
||||
* Depending on requested block read behaviour can be different, therefore
|
||||
* different handlers are used.
|
||||
*/
|
||||
typedef void (*FelicaCommanReadBlockHandler)(
|
||||
FelicaListener* instance,
|
||||
const uint8_t block_number,
|
||||
const uint8_t resp_data_index,
|
||||
FelicaListenerReadCommandResponse* response);
|
||||
|
||||
struct FelicaListener {
|
||||
Nfc* nfc;
|
||||
FelicaData* data;
|
||||
FelicaListenerState state;
|
||||
FelicaAuthentication auth;
|
||||
FelicaBlockData mc_shadow;
|
||||
|
||||
uint8_t request_size_buf;
|
||||
uint8_t block_list_size;
|
||||
uint8_t requested_blocks[FELICA_LISTENER_READ_BLOCK_COUNT_MAX];
|
||||
uint8_t mac_calc_start;
|
||||
bool rc_written;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
@@ -18,4 +75,170 @@ struct FelicaListener {
|
||||
NfcGenericEvent generic_event;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
};
|
||||
|
||||
/** Resets card authentication state after field off, also resets session key and
|
||||
* perform post process operations with some blocks.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
*/
|
||||
void felica_listener_reset(FelicaListener* instance);
|
||||
|
||||
/** Performs WCNT increasing in case of write operation.
|
||||
*
|
||||
* @param data pointer to Felica card data.
|
||||
*/
|
||||
void felica_wcnt_increment(FelicaData* data);
|
||||
|
||||
/** Compares IDm of card loaded for emulation with IDm from request.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request_idm pointer to IDm block from request structure.
|
||||
*
|
||||
* @return True if IDms' are equal, otherwise false.
|
||||
*/
|
||||
bool felica_listener_check_idm(const FelicaListener* instance, const FelicaIDm* request_idm);
|
||||
|
||||
/** This is the first request validation function.
|
||||
*
|
||||
* Its main aim is to check whether the input request is valid in general by
|
||||
* counting the amount of data in request. Function iterates through block list
|
||||
* elements and data, counts real amount of elements and real amount of
|
||||
* coresponding elements data (in case of write operation). If block list
|
||||
* element amount is invalid or request data is missing, such request will be
|
||||
* ignored.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received request.
|
||||
*
|
||||
* @return True if block element list contains valid amount of data,
|
||||
* otherwise false.
|
||||
*/
|
||||
bool felica_listener_check_block_list_size(
|
||||
FelicaListener* instance,
|
||||
FelicaListenerGenericRequest* request);
|
||||
|
||||
/** Used to take first element from block element list.
|
||||
*
|
||||
* Each element in block list can be 2 or 3-bytes length and they can be mixed
|
||||
* together. Therefore before returning the element, function checks whether
|
||||
* there is enough bytes in the request for this element, in order to avoid
|
||||
* memory casting behind the request block.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received request.
|
||||
*
|
||||
* @return Pointer to the first element of the list or NULL if there is not
|
||||
* enough bytes in the request.
|
||||
*/
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_first(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerRequest* request);
|
||||
|
||||
/** Used to take next element from block element list.
|
||||
*
|
||||
* It uses pointer to the previous element, takes its length and calculates
|
||||
* pointer to the next element if there is enough bytes left.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param prev_item pointer to the previous item taken.
|
||||
*
|
||||
* @return Pointer to the next element of the list or NULL if there is not
|
||||
* enough bytes in the request.
|
||||
*/
|
||||
const FelicaBlockListElement* felica_listener_block_list_item_get_next(
|
||||
FelicaListener* instance,
|
||||
const FelicaBlockListElement* prev_item);
|
||||
|
||||
/** Calculates pointer to data blocks in case of write operation, because block
|
||||
* list elements size can vary.
|
||||
*
|
||||
* For calculation it uses previously calculated block list size and
|
||||
* FelicaListenerGenericRequest size.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param generic_request pointer to received request.
|
||||
*
|
||||
* @return Pointer to data blocks for write operation.
|
||||
*/
|
||||
const FelicaListenerWriteBlockData* felica_listener_get_write_request_data_pointer(
|
||||
const FelicaListener* const instance,
|
||||
const FelicaListenerGenericRequest* const generic_request);
|
||||
|
||||
/** Function validates write request data and sets Felica SF1 and SF2 flags
|
||||
* directly to the response.
|
||||
*
|
||||
* When something is wrong with data in the request (for example non-existing
|
||||
* block is requested) this function sets proper SF1 and SF2 flags which will be
|
||||
* then send to reader. When every thing is fine SF1 and SF2 are 0. Also this
|
||||
* function performs authentiocation checks by MAC_A calculation if request
|
||||
* contains MAC_A.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received write request.
|
||||
* @param data pointer to data which request wants to write.
|
||||
* @param response pointer to the response to where SF1 and SF2 flags will
|
||||
* be populated.
|
||||
*
|
||||
* @return True if request is fine also SF1 and SF2 will be 0. False if
|
||||
* something is wrong and SF1, SF2 will provide more detailed
|
||||
* information about the error.
|
||||
*/
|
||||
bool felica_listener_validate_write_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerWriteRequest* const request,
|
||||
const FelicaListenerWriteBlockData* const data,
|
||||
FelicaListenerWriteCommandResponse* response);
|
||||
|
||||
/** Function validates read request data and sets Felica SF1 and SF2 flags
|
||||
* directly to the response.
|
||||
*
|
||||
* When something is wrong with data in the request (for example non-existing
|
||||
* block is requested) this function sets proper SF1 and SF2 flags which will be
|
||||
* then send to reader. In such case there will be no any data in the request,
|
||||
* only flags. In case when request is fine SF1 and SF2 will be 0 and data will
|
||||
* be populated to the response after them. Attention! This function doesn't
|
||||
* populate any data, it only allows further processing where those data will be
|
||||
* populated.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param request pointer to received write request.
|
||||
* @param resp_header pointer to the response to where SF1 and SF2 and
|
||||
* data flags will be populated.
|
||||
*
|
||||
* @return True if request is fine also SF1 and SF2 will be 0. False if
|
||||
* something is wrong and SF1, SF2 will provide more detailed
|
||||
* information about the error.
|
||||
*/
|
||||
bool felica_listener_validate_read_request_and_set_sf(
|
||||
FelicaListener* instance,
|
||||
const FelicaListenerReadRequest* const request,
|
||||
FelicaCommandResponseHeader* resp_header);
|
||||
|
||||
/** Function returns appropiate block handler for processing read operation
|
||||
* depending on number of block.
|
||||
*
|
||||
* @param block_number number of block.
|
||||
*
|
||||
* @return pointer to block handler function.
|
||||
*/
|
||||
FelicaCommanReadBlockHandler felica_listener_get_read_block_handler(const uint8_t block_number);
|
||||
|
||||
/** Function returns appropiate block handler for processing write operation
|
||||
* depending on number of block.
|
||||
*
|
||||
* @param block_number number of block.
|
||||
*
|
||||
* @return pointer to block handler function.
|
||||
*/
|
||||
FelicaCommandWriteBlockHandler felica_listener_get_write_block_handler(const uint8_t block_number);
|
||||
|
||||
/** Sends response data from buffer to reader.
|
||||
*
|
||||
* @param instance pointer to the listener instance to be used.
|
||||
* @param tx_buffer buffer with response data.
|
||||
*
|
||||
* @return error code or FelicaErrorNone.
|
||||
*/
|
||||
FelicaError
|
||||
felica_listener_frame_exchange(const FelicaListener* instance, const BitBuffer* tx_buffer);
|
||||
|
||||
@@ -24,22 +24,6 @@ typedef enum {
|
||||
FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */
|
||||
} FelicaPollerEventType;
|
||||
|
||||
/**
|
||||
* @brief Stucture for holding Felica session key which is calculated from rc and ck.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaSessionKey;
|
||||
|
||||
/**
|
||||
* @brief Structure used to hold authentication related fields.
|
||||
*/
|
||||
typedef struct {
|
||||
mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */
|
||||
FelicaSessionKey session_key; /**< Calculated session key. */
|
||||
FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */
|
||||
} FelicaAuthentication;
|
||||
|
||||
/**
|
||||
* @brief Felica poller event data.
|
||||
*/
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
|
||||
#define TAG "FelicaPoller"
|
||||
#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U)
|
||||
#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U)
|
||||
|
||||
#define FELICA_SERVICE_RW_ACCESS (0x0009U)
|
||||
#define FELICA_SERVICE_RO_ACCESS (0x000BU)
|
||||
|
||||
static FelicaError felica_poller_process_error(NfcError error) {
|
||||
switch(error) {
|
||||
|
||||
@@ -55,41 +55,6 @@ typedef struct {
|
||||
uint8_t request_data[2];
|
||||
} FelicaPollerPollingResponse;
|
||||
|
||||
typedef struct {
|
||||
uint8_t service_code : 4;
|
||||
uint8_t access_mode : 3;
|
||||
uint8_t length : 1;
|
||||
uint8_t block_number;
|
||||
} FelicaBlockListElement;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t code;
|
||||
FelicaIDm idm;
|
||||
uint8_t service_num;
|
||||
uint16_t service_code;
|
||||
uint8_t block_count;
|
||||
} FelicaCommandHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
uint8_t block_count;
|
||||
uint8_t data[];
|
||||
} FelicaPollerReadCommandResponse;
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t response_code;
|
||||
FelicaIDm idm;
|
||||
uint8_t SF1;
|
||||
uint8_t SF2;
|
||||
} FelicaPollerWriteCommandResponse;
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "felica_poller_sync.h"
|
||||
|
||||
#include "felica_poller_i.h"
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define FELICA_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0)
|
||||
|
||||
typedef struct {
|
||||
FelicaAuthenticationContext auth_ctx;
|
||||
FuriThreadId thread_id;
|
||||
FelicaError error;
|
||||
FelicaData data;
|
||||
} Felica_PollerContext;
|
||||
|
||||
NfcCommand felica_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolFelica);
|
||||
|
||||
Felica_PollerContext* poller_context = context;
|
||||
FelicaPoller* felica_poller = event.instance;
|
||||
|
||||
FelicaPollerEvent* felica_event = event.event_data;
|
||||
|
||||
if(felica_event->type == FelicaPollerEventTypeReady ||
|
||||
felica_event->type == FelicaPollerEventTypeIncomplete) {
|
||||
felica_copy(&poller_context->data, felica_poller->data);
|
||||
} else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
|
||||
felica_event->data->auth_context->skip_auth = poller_context->auth_ctx.skip_auth;
|
||||
memcpy(
|
||||
felica_event->data->auth_context->card_key.data,
|
||||
poller_context->auth_ctx.card_key.data,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
furi_thread_flags_set(poller_context->thread_id, FELICA_POLLER_FLAG_COMMAND_COMPLETE);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key) {
|
||||
furi_check(nfc);
|
||||
furi_check(data);
|
||||
|
||||
Felica_PollerContext poller_context = {};
|
||||
if(card_key == NULL) {
|
||||
poller_context.auth_ctx.skip_auth = true;
|
||||
} else {
|
||||
poller_context.auth_ctx.skip_auth = false;
|
||||
memcpy(poller_context.auth_ctx.card_key.data, card_key->data, FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolFelica);
|
||||
nfc_poller_start(poller, felica_poller_read_callback, &poller_context);
|
||||
furi_thread_flags_wait(FELICA_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(FELICA_POLLER_FLAG_COMMAND_COMPLETE);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(poller_context.error == FelicaErrorNone) {
|
||||
*data = poller_context.data;
|
||||
}
|
||||
|
||||
return poller_context.error;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "felica.h"
|
||||
#include <nfc/nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
FelicaError felica_poller_sync_read(Nfc* nfc, FelicaData* data, const FelicaCardKey* card_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "iso14443_3a_poller_sync.h"
|
||||
|
||||
#include "iso14443_3a_poller_i.h"
|
||||
#include "iso14443_3a_poller_i.h" // IWYU pragma: keep
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include "iso14443_3b_i.h"
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "iso14443_4b_i.h"
|
||||
#include "iso14443_4b_i.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define MF_PLUS_PROTOCOL_NAME "Mifare Plus"
|
||||
|
||||
static const char* mf_plus_type_strings[] = {
|
||||
[MfPlusTypeS] = "Plus S",
|
||||
[MfPlusTypeX] = "Plus X",
|
||||
[MfPlusTypeSE] = "Plus SE",
|
||||
[MfPlusTypeEV1] = "Plus EV1",
|
||||
[MfPlusTypeEV2] = "Plus EV2",
|
||||
[MfPlusTypePlus] = "Plus",
|
||||
[MfPlusTypeUnknown] = "Unknown",
|
||||
};
|
||||
|
||||
static const char* mf_plus_size_strings[] = {
|
||||
[MfPlusSize1K] = "1K",
|
||||
[MfPlusSize2K] = "2K",
|
||||
[MfPlusSize4K] = "4K",
|
||||
[MfPlusSizeUnknown] = "Unknown",
|
||||
};
|
||||
|
||||
static const char* mf_plus_security_level_strings[] = {
|
||||
[MfPlusSecurityLevel0] = "SL0",
|
||||
[MfPlusSecurityLevel1] = "SL1",
|
||||
[MfPlusSecurityLevel2] = "SL2",
|
||||
[MfPlusSecurityLevel3] = "SL3",
|
||||
[MfPlusSecurityLevelUnknown] = "Unknown",
|
||||
};
|
||||
|
||||
const NfcDeviceBase nfc_device_mf_plus = {
|
||||
.protocol_name = MF_PLUS_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)mf_plus_alloc,
|
||||
.free = (NfcDeviceFree)mf_plus_free,
|
||||
.reset = (NfcDeviceReset)mf_plus_reset,
|
||||
.copy = (NfcDeviceCopy)mf_plus_copy,
|
||||
.verify = (NfcDeviceVerify)mf_plus_verify,
|
||||
.load = (NfcDeviceLoad)mf_plus_load,
|
||||
.save = (NfcDeviceSave)mf_plus_save,
|
||||
.is_equal = (NfcDeviceEqual)mf_plus_is_equal,
|
||||
.get_name = (NfcDeviceGetName)mf_plus_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)mf_plus_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)mf_plus_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)mf_plus_get_base_data,
|
||||
};
|
||||
|
||||
MfPlusData* mf_plus_alloc(void) {
|
||||
MfPlusData* data = malloc(sizeof(MfPlusData));
|
||||
data->device_name = furi_string_alloc();
|
||||
data->iso14443_4a_data = iso14443_4a_alloc();
|
||||
|
||||
data->type = MfPlusTypeUnknown;
|
||||
data->security_level = MfPlusSecurityLevelUnknown;
|
||||
data->size = MfPlusSizeUnknown;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void mf_plus_free(MfPlusData* data) {
|
||||
furi_check(data);
|
||||
|
||||
furi_string_free(data->device_name);
|
||||
iso14443_4a_free(data->iso14443_4a_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mf_plus_reset(MfPlusData* data) {
|
||||
furi_check(data);
|
||||
|
||||
iso14443_4a_reset(data->iso14443_4a_data);
|
||||
memset(&data->version, 0, sizeof(data->version));
|
||||
furi_string_reset(data->device_name);
|
||||
data->type = MfPlusTypeUnknown;
|
||||
data->security_level = MfPlusSecurityLevelUnknown;
|
||||
data->size = MfPlusSizeUnknown;
|
||||
}
|
||||
|
||||
void mf_plus_copy(MfPlusData* data, const MfPlusData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data);
|
||||
data->version = other->version;
|
||||
data->type = other->type;
|
||||
data->security_level = other->security_level;
|
||||
data->size = other->size;
|
||||
}
|
||||
|
||||
bool mf_plus_verify(MfPlusData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal_str(device_type, MF_PLUS_PROTOCOL_NAME);
|
||||
}
|
||||
|
||||
bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_check(data);
|
||||
|
||||
bool success = false;
|
||||
do {
|
||||
if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break;
|
||||
if(!mf_plus_version_load(&data->version, ff)) break;
|
||||
if(!mf_plus_type_load(&data->type, ff)) break;
|
||||
if(!mf_plus_security_level_load(&data->security_level, ff)) break;
|
||||
if(!mf_plus_size_load(&data->size, ff)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff) {
|
||||
furi_check(data);
|
||||
|
||||
bool success = false;
|
||||
do {
|
||||
if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break;
|
||||
if(!flipper_format_write_comment_cstr(ff, MF_PLUS_PROTOCOL_NAME " specific data")) break;
|
||||
if(!mf_plus_version_save(&data->version, ff)) break;
|
||||
if(!mf_plus_type_save(&data->type, ff)) break;
|
||||
if(!mf_plus_security_level_save(&data->security_level, ff)) break;
|
||||
if(!mf_plus_size_save(&data->size, ff)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
bool equal = false;
|
||||
do {
|
||||
if(!iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data)) break;
|
||||
if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break;
|
||||
if(data->security_level != other->security_level) break;
|
||||
if(data->type != other->type) break;
|
||||
if(data->size != other->size) break;
|
||||
equal = true;
|
||||
} while(false);
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type) {
|
||||
furi_check(data);
|
||||
|
||||
if(name_type == NfcDeviceNameTypeFull) {
|
||||
furi_string_printf(
|
||||
data->device_name,
|
||||
"Mifare %s %s %s",
|
||||
mf_plus_type_strings[data->type], // Includes "Plus" for regular Mifare Plus cards
|
||||
mf_plus_size_strings[data->size],
|
||||
mf_plus_security_level_strings[data->security_level]);
|
||||
} else if(name_type == NfcDeviceNameTypeShort) {
|
||||
furi_string_set_str(data->device_name, MF_PLUS_PROTOCOL_NAME);
|
||||
} else {
|
||||
furi_crash("Unexpected name type");
|
||||
}
|
||||
|
||||
return furi_string_get_cstr(data->device_name);
|
||||
}
|
||||
|
||||
const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len) {
|
||||
furi_check(data);
|
||||
|
||||
return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len);
|
||||
}
|
||||
|
||||
bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_check(data);
|
||||
|
||||
return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len);
|
||||
}
|
||||
Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data) {
|
||||
furi_check(data);
|
||||
|
||||
return data->iso14443_4a_data;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_PLUS_UID_SIZE_MAX (7)
|
||||
#define MF_PLUS_BATCH_SIZE (5)
|
||||
|
||||
#define MF_PLUS_CMD_GET_VERSION (0x60)
|
||||
|
||||
typedef enum {
|
||||
MfPlusErrorNone,
|
||||
MfPlusErrorUnknown,
|
||||
MfPlusErrorNotPresent,
|
||||
MfPlusErrorProtocol,
|
||||
MfPlusErrorAuth,
|
||||
MfPlusErrorPartialRead,
|
||||
MfPlusErrorTimeout,
|
||||
} MfPlusError;
|
||||
|
||||
typedef enum {
|
||||
MfPlusTypePlus,
|
||||
MfPlusTypeEV1,
|
||||
MfPlusTypeEV2,
|
||||
MfPlusTypeS,
|
||||
MfPlusTypeSE,
|
||||
MfPlusTypeX,
|
||||
|
||||
MfPlusTypeUnknown,
|
||||
MfPlusTypeNum,
|
||||
} MfPlusType;
|
||||
|
||||
typedef enum {
|
||||
MfPlusSize1K,
|
||||
MfPlusSize2K,
|
||||
MfPlusSize4K,
|
||||
|
||||
MfPlusSizeUnknown,
|
||||
MfPlusSizeNum,
|
||||
} MfPlusSize;
|
||||
|
||||
typedef enum {
|
||||
MfPlusSecurityLevel0,
|
||||
MfPlusSecurityLevel1,
|
||||
MfPlusSecurityLevel2,
|
||||
MfPlusSecurityLevel3,
|
||||
|
||||
MfPlusSecurityLevelUnknown,
|
||||
MfPlusSecurityLevelNum,
|
||||
} MfPlusSecurityLevel;
|
||||
|
||||
typedef struct {
|
||||
uint8_t hw_vendor;
|
||||
uint8_t hw_type;
|
||||
uint8_t hw_subtype;
|
||||
uint8_t hw_major;
|
||||
uint8_t hw_minor;
|
||||
uint8_t hw_storage;
|
||||
uint8_t hw_proto;
|
||||
|
||||
uint8_t sw_vendor;
|
||||
uint8_t sw_type;
|
||||
uint8_t sw_subtype;
|
||||
uint8_t sw_major;
|
||||
uint8_t sw_minor;
|
||||
uint8_t sw_storage;
|
||||
uint8_t sw_proto;
|
||||
|
||||
uint8_t uid[MF_PLUS_UID_SIZE_MAX];
|
||||
uint8_t batch[MF_PLUS_BATCH_SIZE];
|
||||
uint8_t prod_week;
|
||||
uint8_t prod_year;
|
||||
} MfPlusVersion;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4aData* iso14443_4a_data;
|
||||
MfPlusVersion version;
|
||||
MfPlusType type;
|
||||
MfPlusSize size;
|
||||
MfPlusSecurityLevel security_level;
|
||||
FuriString* device_name;
|
||||
} MfPlusData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_mf_plus;
|
||||
|
||||
MfPlusData* mf_plus_alloc(void);
|
||||
|
||||
void mf_plus_free(MfPlusData* data);
|
||||
|
||||
void mf_plus_reset(MfPlusData* data);
|
||||
|
||||
void mf_plus_copy(MfPlusData* data, const MfPlusData* other);
|
||||
|
||||
bool mf_plus_verify(MfPlusData* data, const FuriString* device_type);
|
||||
|
||||
bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other);
|
||||
|
||||
const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len);
|
||||
|
||||
bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,406 @@
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#define MF_PLUS_FFF_VERSION_KEY \
|
||||
MF_PLUS_FFF_PICC_PREFIX " " \
|
||||
"Version"
|
||||
|
||||
#define MF_PLUS_FFF_SECURITY_LEVEL_KEY "Security Level"
|
||||
#define MF_PLUS_FFF_CARD_TYPE_KEY "Card Type"
|
||||
#define MF_PLUS_FFF_MEMORY_SIZE_KEY "Memory Size"
|
||||
|
||||
#define TAG "MfPlus"
|
||||
|
||||
const uint8_t mf_plus_ats_t1_tk_values[][7] = {
|
||||
{0xC1, 0x05, 0x2F, 0x2F, 0x00, 0x35, 0xC7}, // Mifare Plus S
|
||||
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X
|
||||
{0xC1, 0x05, 0x2F, 0x2F, 0x00, 0xF6, 0xD1}, // Mifare Plus SE
|
||||
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE
|
||||
};
|
||||
|
||||
MfPlusError mf_plus_get_type_from_version(
|
||||
const Iso14443_4aData* iso14443_4a_data,
|
||||
MfPlusData* mf_plus_data) {
|
||||
furi_assert(iso14443_4a_data);
|
||||
furi_assert(mf_plus_data);
|
||||
|
||||
MfPlusError error = MfPlusErrorProtocol;
|
||||
|
||||
if(mf_plus_data->version.hw_major == 0x02 || mf_plus_data->version.hw_major == 0x82) {
|
||||
error = MfPlusErrorNone;
|
||||
if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) {
|
||||
// Mifare Plus 2K SL2
|
||||
mf_plus_data->type = MfPlusTypePlus;
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel2;
|
||||
FURI_LOG_D(TAG, "Mifare Plus 2K SL2");
|
||||
} else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) {
|
||||
// Mifare Plus 4K SL3
|
||||
mf_plus_data->type = MfPlusTypePlus;
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||
FURI_LOG_D(TAG, "Mifare Plus 4K SL3");
|
||||
} else {
|
||||
// Mifare Plus EV1/EV2
|
||||
|
||||
// Revision
|
||||
switch(mf_plus_data->version.hw_major) {
|
||||
case 0x11:
|
||||
mf_plus_data->type = MfPlusTypeEV1;
|
||||
FURI_LOG_D(TAG, "Mifare Plus EV1");
|
||||
break;
|
||||
case 0x22:
|
||||
mf_plus_data->type = MfPlusTypeEV2;
|
||||
FURI_LOG_D(TAG, "Mifare Plus EV2");
|
||||
break;
|
||||
default:
|
||||
mf_plus_data->type = MfPlusTypeUnknown;
|
||||
FURI_LOG_D(TAG, "Unknown Mifare Plus EV type");
|
||||
break;
|
||||
}
|
||||
|
||||
// Storage size
|
||||
switch(mf_plus_data->version.hw_storage) {
|
||||
case 0x16:
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
FURI_LOG_D(TAG, "2K");
|
||||
break;
|
||||
case 0x18:
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
FURI_LOG_D(TAG, "4K");
|
||||
break;
|
||||
default:
|
||||
mf_plus_data->size = MfPlusSizeUnknown;
|
||||
FURI_LOG_D(TAG, "Unknown storage size");
|
||||
break;
|
||||
}
|
||||
|
||||
// Security level
|
||||
if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) {
|
||||
// Mifare Plus EV1/2 SL3
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||
FURI_LOG_D(TAG, "Miare Plus EV1/2 SL3");
|
||||
} else {
|
||||
// Mifare Plus EV1/2 SL1
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
FURI_LOG_D(TAG, "Miare Plus EV1/2 SL1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfPlusError
|
||||
mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) {
|
||||
furi_assert(iso4_data);
|
||||
furi_assert(mf_plus_data);
|
||||
|
||||
MfPlusError error = MfPlusErrorProtocol;
|
||||
|
||||
switch(iso4_data->iso14443_3a_data->sak) {
|
||||
case 0x08:
|
||||
if(memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[0],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus S 2K SL1
|
||||
mf_plus_data->type = MfPlusTypeS;
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 2K SL1");
|
||||
error = MfPlusErrorNone;
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[1],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus X 2K SL1
|
||||
mf_plus_data->type = MfPlusTypeX;
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 2K SL1");
|
||||
error = MfPlusErrorNone;
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[2],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 ||
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[3],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus SE 1K SL1
|
||||
mf_plus_data->type = MfPlusTypeSE;
|
||||
mf_plus_data->size = MfPlusSize1K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1");
|
||||
error = MfPlusErrorNone;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type");
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x18:
|
||||
if(memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[0],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus S 4K SL1
|
||||
mf_plus_data->type = MfPlusTypeS;
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 4K SL1");
|
||||
error = MfPlusErrorNone;
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[1],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus X 4K SL1
|
||||
mf_plus_data->type = MfPlusTypeX;
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 4K SL1");
|
||||
error = MfPlusErrorNone;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type");
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x20:
|
||||
if(memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[0],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus S 2/4K SL3
|
||||
FURI_LOG_D(TAG, "Mifare Plus S SL3");
|
||||
mf_plus_data->type = MfPlusTypeS;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||
|
||||
if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) {
|
||||
// Mifare Plus S 2K SL3
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 2K SL3");
|
||||
error = MfPlusErrorNone;
|
||||
} else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) {
|
||||
// Mifare Plus S 4K SL3
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 4K SL3");
|
||||
error = MfPlusErrorNone;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)");
|
||||
}
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[1],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
mf_plus_data->type = MfPlusTypeX;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||
FURI_LOG_D(TAG, "Mifare Plus X SL3");
|
||||
|
||||
if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) {
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 2K SL3");
|
||||
error = MfPlusErrorNone;
|
||||
} else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) {
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 4K SL3");
|
||||
error = MfPlusErrorNone;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type");
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
|
||||
}
|
||||
|
||||
return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol;
|
||||
}
|
||||
|
||||
bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff) {
|
||||
return flipper_format_read_hex(
|
||||
ff, MF_PLUS_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfPlusVersion));
|
||||
}
|
||||
|
||||
bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff) {
|
||||
FuriString* security_level_string = furi_string_alloc();
|
||||
flipper_format_read_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string);
|
||||
|
||||
// Take the last character of the string
|
||||
char security_level_char = furi_string_get_char(
|
||||
security_level_string, furi_string_utf8_length(security_level_string) - 1);
|
||||
|
||||
switch(security_level_char) {
|
||||
case '0':
|
||||
*data = MfPlusSecurityLevel0;
|
||||
break;
|
||||
case '1':
|
||||
*data = MfPlusSecurityLevel1;
|
||||
break;
|
||||
case '2':
|
||||
*data = MfPlusSecurityLevel2;
|
||||
break;
|
||||
case '3':
|
||||
*data = MfPlusSecurityLevel3;
|
||||
break;
|
||||
default:
|
||||
*data = MfPlusSecurityLevelUnknown;
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(security_level_string);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff) {
|
||||
FuriString* type_string = furi_string_alloc();
|
||||
flipper_format_read_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string);
|
||||
|
||||
if(furi_string_equal_str(type_string, "Mifare Plus")) {
|
||||
*data = MfPlusTypePlus;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus X")) {
|
||||
*data = MfPlusTypeX;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus S")) {
|
||||
*data = MfPlusTypeS;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus SE")) {
|
||||
*data = MfPlusTypeSE;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus EV1")) {
|
||||
*data = MfPlusTypeEV1;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus EV2")) {
|
||||
*data = MfPlusTypeEV2;
|
||||
} else {
|
||||
*data = MfPlusTypeUnknown;
|
||||
}
|
||||
|
||||
furi_string_free(type_string);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff) {
|
||||
FuriString* size_string = furi_string_alloc();
|
||||
flipper_format_read_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string);
|
||||
|
||||
if(furi_string_equal_str(size_string, "1K")) {
|
||||
*data = MfPlusSize1K;
|
||||
} else if(furi_string_equal_str(size_string, "2K")) {
|
||||
*data = MfPlusSize2K;
|
||||
} else if(furi_string_equal_str(size_string, "4K")) {
|
||||
*data = MfPlusSize4K;
|
||||
} else {
|
||||
*data = MfPlusSizeUnknown;
|
||||
}
|
||||
|
||||
furi_string_free(size_string);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff) {
|
||||
return flipper_format_write_hex(
|
||||
ff, MF_PLUS_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfPlusVersion));
|
||||
}
|
||||
|
||||
bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff) {
|
||||
FuriString* security_level_string = furi_string_alloc();
|
||||
|
||||
switch(*data) {
|
||||
case MfPlusSecurityLevel0:
|
||||
furi_string_cat(security_level_string, "SL0");
|
||||
break;
|
||||
case MfPlusSecurityLevel1:
|
||||
furi_string_cat(security_level_string, "SL1");
|
||||
break;
|
||||
case MfPlusSecurityLevel2:
|
||||
furi_string_cat(security_level_string, "SL2");
|
||||
break;
|
||||
case MfPlusSecurityLevel3:
|
||||
furi_string_cat(security_level_string, "SL3");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat(security_level_string, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
bool success =
|
||||
flipper_format_write_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string);
|
||||
furi_string_free(security_level_string);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff) {
|
||||
FuriString* type_string = furi_string_alloc();
|
||||
|
||||
switch(*data) {
|
||||
case MfPlusTypePlus:
|
||||
furi_string_cat(type_string, "Mifare Plus");
|
||||
break;
|
||||
case MfPlusTypeX:
|
||||
furi_string_cat(type_string, "Mifare Plus X");
|
||||
break;
|
||||
case MfPlusTypeS:
|
||||
furi_string_cat(type_string, "Mifare Plus S");
|
||||
break;
|
||||
case MfPlusTypeSE:
|
||||
furi_string_cat(type_string, "Mifare Plus SE");
|
||||
break;
|
||||
case MfPlusTypeEV1:
|
||||
furi_string_cat(type_string, "Mifare Plus EV1");
|
||||
break;
|
||||
case MfPlusTypeEV2:
|
||||
furi_string_cat(type_string, "Mifare Plus EV2");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat(type_string, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
bool success = flipper_format_write_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string);
|
||||
furi_string_free(type_string);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) {
|
||||
FuriString* size_string = furi_string_alloc();
|
||||
|
||||
switch(*data) {
|
||||
case MfPlusSize1K:
|
||||
furi_string_cat(size_string, "1K");
|
||||
break;
|
||||
case MfPlusSize2K:
|
||||
furi_string_cat(size_string, "2K");
|
||||
break;
|
||||
case MfPlusSize4K:
|
||||
furi_string_cat(size_string, "4K");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat(size_string, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
bool success = flipper_format_write_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string);
|
||||
furi_string_free(size_string);
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_plus.h"
|
||||
|
||||
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
|
||||
|
||||
MfPlusError mf_plus_get_type_from_version(
|
||||
const Iso14443_4aData* iso14443_4a_data,
|
||||
MfPlusData* mf_plus_data);
|
||||
|
||||
MfPlusError mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data);
|
||||
|
||||
MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff);
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "mf_plus_poller_i.h"
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfPlusPoller"
|
||||
|
||||
#define MF_PLUS_BUF_SIZE (64U)
|
||||
#define MF_PLUS_RESULT_BUF_SIZE (512U)
|
||||
|
||||
typedef NfcCommand (*MfPlusPollerReadHandler)(MfPlusPoller* instance);
|
||||
|
||||
const MfPlusData* mf_plus_poller_get_data(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) {
|
||||
furi_assert(iso14443_4a_poller);
|
||||
|
||||
MfPlusPoller* instance = malloc(sizeof(MfPlusPoller));
|
||||
|
||||
instance->iso14443_4a_poller = iso14443_4a_poller;
|
||||
|
||||
instance->data = mf_plus_alloc();
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE);
|
||||
instance->input_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE);
|
||||
instance->result_buffer = bit_buffer_alloc(MF_PLUS_RESULT_BUF_SIZE);
|
||||
|
||||
instance->general_event.protocol = NfcProtocolMfPlus;
|
||||
instance->general_event.event_data = &instance->mfp_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
instance->mfp_event.data = &instance->mfp_event_data;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_idle(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_reset(instance->result_buffer);
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
iso14443_4a_copy(
|
||||
instance->data->iso14443_4a_data,
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller));
|
||||
|
||||
instance->state = MfPlusPollerStateReadVersion;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_read_version(MfPlusPoller* instance) {
|
||||
MfPlusError error = mf_plus_poller_read_version(instance, &instance->data->version);
|
||||
if(error == MfPlusErrorNone) {
|
||||
instance->state = MfPlusPollerStateParseVersion;
|
||||
} else {
|
||||
instance->state = MfPlusPollerStateParseIso4;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_parse_version(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
MfPlusError error = mf_plus_get_type_from_version(
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data);
|
||||
if(error == MfPlusErrorNone) {
|
||||
instance->state = MfPlusPollerStateReadSuccess;
|
||||
} else {
|
||||
instance->error = error;
|
||||
instance->state = MfPlusPollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_parse_iso4(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
MfPlusError error = mf_plus_get_type_from_iso4(
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data);
|
||||
if(error == MfPlusErrorNone) {
|
||||
instance->state = MfPlusPollerStateReadSuccess;
|
||||
} else {
|
||||
instance->error = error;
|
||||
instance->state = MfPlusPollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_read_failed(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
FURI_LOG_D(TAG, "Read failed");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
|
||||
instance->mfp_event.type = MfPlusPollerEventTypeReadFailed;
|
||||
instance->mfp_event.data->error = instance->error;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfPlusPollerStateIdle;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_read_success(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
FURI_LOG_D(TAG, "Read success");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
|
||||
instance->mfp_event.type = MfPlusPollerEventTypeReadSuccess;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const MfPlusPollerReadHandler mf_plus_poller_read_handler[MfPlusPollerStateNum] = {
|
||||
[MfPlusPollerStateIdle] = mf_plus_poller_handler_idle,
|
||||
[MfPlusPollerStateReadVersion] = mf_plus_poller_handler_read_version,
|
||||
[MfPlusPollerStateParseVersion] = mf_plus_poller_handler_parse_version,
|
||||
[MfPlusPollerStateParseIso4] = mf_plus_poller_handler_parse_iso4,
|
||||
[MfPlusPollerStateReadFailed] = mf_plus_poller_handler_read_failed,
|
||||
[MfPlusPollerStateReadSuccess] = mf_plus_poller_handler_read_success,
|
||||
};
|
||||
|
||||
static void mf_plus_poller_set_callback(
|
||||
MfPlusPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol = NfcProtocolIso14443_4a);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
MfPlusPoller* instance = context;
|
||||
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||
command = mf_plus_poller_read_handler[instance->state](instance);
|
||||
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
|
||||
instance->mfp_event.type = MfPlusPollerEventTypeReadFailed;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void mf_plus_poller_free(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
bit_buffer_free(instance->input_buffer);
|
||||
bit_buffer_free(instance->result_buffer);
|
||||
mf_plus_free(instance->data);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol = NfcProtocolIso14443_4a);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
MfPlusPoller* instance = context;
|
||||
Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||
|
||||
MfPlusError error = MfPlusErrorUnknown;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||
error = mf_plus_poller_read_version(instance, &instance->data->version);
|
||||
if(error == MfPlusErrorNone) {
|
||||
error = mf_plus_get_type_from_version(
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data);
|
||||
} else {
|
||||
error = mf_plus_get_type_from_iso4(
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data);
|
||||
}
|
||||
}
|
||||
|
||||
return (error == MfPlusErrorNone);
|
||||
}
|
||||
|
||||
const NfcPollerBase mf_plus_poller = {
|
||||
.alloc = (NfcPollerAlloc)mf_plus_poller_alloc,
|
||||
.free = (NfcPollerFree)mf_plus_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)mf_plus_poller_set_callback,
|
||||
.run = (NfcPollerRun)mf_plus_poller_run,
|
||||
.detect = (NfcPollerDetect)mf_plus_poller_detect,
|
||||
.get_data = (NfcPollerGetData)mf_plus_poller_get_data,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_plus.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MIFARE Plus poller opaque type definition.
|
||||
*/
|
||||
typedef struct MfPlusPoller MfPlusPoller;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible MfPlus poller event types.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
MfPlusPollerEventTypeReadSuccess, /**< Card was read successfully. */
|
||||
MfPlusPollerEventTypeReadFailed, /**< Poller failed to read the card. */
|
||||
} MfPlusPollerEventType;
|
||||
|
||||
/**
|
||||
* @brief MIFARE Plus poller event data.
|
||||
*/
|
||||
typedef union {
|
||||
MfPlusError error; /**< Error code indicating card reading fail reason. */
|
||||
} MfPlusPollerEventData;
|
||||
|
||||
/**
|
||||
* @brief MIFARE Plus poller event structure.
|
||||
*
|
||||
* Upon emission of an event, an instance of this struct will be passed to the callback.
|
||||
*/
|
||||
typedef struct {
|
||||
MfPlusPollerEventType type; /**< Type of emitted event. */
|
||||
MfPlusPollerEventData* data; /**< Pointer to event specific data. */
|
||||
} MfPlusPollerEvent;
|
||||
|
||||
/**
|
||||
* @brief Read MfPlus card version.
|
||||
*
|
||||
* Must ONLY be used inside the callback function.
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[out] data pointer to the MfPlusVersion structure to be filled with version data.
|
||||
* @return MfPlusErrorNone on success, an error code on failure.
|
||||
*/
|
||||
MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase mf_plus_poller;
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "mf_plus_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#define TAG "MfPlusPoller"
|
||||
|
||||
MfPlusError mf_plus_process_error(Iso14443_4aError error) {
|
||||
switch(error) {
|
||||
case Iso14443_4aErrorNone:
|
||||
return MfPlusErrorNone;
|
||||
case Iso14443_4aErrorNotPresent:
|
||||
return MfPlusErrorNotPresent;
|
||||
case Iso14443_4aErrorTimeout:
|
||||
return MfPlusErrorTimeout;
|
||||
default:
|
||||
return MfPlusErrorProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
MfPlusError mf_plus_poller_send_chunk(
|
||||
MfPlusPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->iso14443_4a_poller);
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
|
||||
instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer);
|
||||
MfPlusError error = mf_plus_process_error(iso14443_4a_error);
|
||||
|
||||
if(error == MfPlusErrorNone) {
|
||||
bit_buffer_copy(rx_buffer, instance->rx_buffer);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) {
|
||||
furi_check(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION);
|
||||
|
||||
MfPlusError error =
|
||||
mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer);
|
||||
if(error == MfPlusErrorNone) {
|
||||
error = mf_plus_version_parse(data, instance->result_buffer);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_plus_poller.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_PLUS_FWT_FC (60000)
|
||||
|
||||
typedef enum {
|
||||
MfPlusCardStateDetected,
|
||||
MfPlusCardStateLost,
|
||||
} MfPlusCardState;
|
||||
|
||||
typedef enum {
|
||||
MfPlusPollerStateIdle,
|
||||
MfPlusPollerStateReadVersion,
|
||||
MfPlusPollerStateParseVersion,
|
||||
MfPlusPollerStateParseIso4,
|
||||
MfPlusPollerStateReadFailed,
|
||||
MfPlusPollerStateReadSuccess,
|
||||
|
||||
MfPlusPollerStateNum,
|
||||
} MfPlusPollerState;
|
||||
|
||||
struct MfPlusPoller {
|
||||
Iso14443_4aPoller* iso14443_4a_poller;
|
||||
|
||||
MfPlusData* data;
|
||||
MfPlusPollerState state;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
BitBuffer* input_buffer;
|
||||
BitBuffer* result_buffer;
|
||||
|
||||
MfPlusError error;
|
||||
NfcGenericEvent general_event;
|
||||
MfPlusPollerEvent mfp_event;
|
||||
MfPlusPollerEventData mfp_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
MfPlusError mf_plus_process_error(Iso14443_4aError error);
|
||||
|
||||
MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller);
|
||||
|
||||
void mf_plus_poller_free(MfPlusPoller* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -570,6 +570,22 @@ uint16_t mf_ultralight_get_config_page_num(MfUltralightType type) {
|
||||
return mf_ultralight_features[type].config_page;
|
||||
}
|
||||
|
||||
uint8_t mf_ultralight_get_write_end_page(MfUltralightType type) {
|
||||
furi_check(type < MfUltralightTypeNum);
|
||||
furi_assert(
|
||||
type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21 ||
|
||||
type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 ||
|
||||
type == MfUltralightTypeNTAG216);
|
||||
|
||||
uint8_t end_page = mf_ultralight_get_config_page_num(type);
|
||||
if(type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 ||
|
||||
type == MfUltralightTypeNTAG216) {
|
||||
end_page -= 1;
|
||||
}
|
||||
|
||||
return end_page;
|
||||
}
|
||||
|
||||
uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type) {
|
||||
furi_check(type < MfUltralightTypeNum);
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type);
|
||||
|
||||
uint16_t mf_ultralight_get_config_page_num(MfUltralightType type);
|
||||
|
||||
uint8_t mf_ultralight_get_write_end_page(MfUltralightType type);
|
||||
|
||||
uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type);
|
||||
|
||||
bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page_num);
|
||||
|
||||
@@ -616,7 +616,7 @@ static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* i
|
||||
|
||||
do {
|
||||
const MfUltralightData* write_data = instance->mfu_event.data->write_data;
|
||||
uint8_t end_page = mf_ultralight_get_config_page_num(write_data->type) - 1;
|
||||
uint8_t end_page = mf_ultralight_get_write_end_page(write_data->type);
|
||||
if(instance->current_page == end_page) {
|
||||
instance->state = MfUltralightPollerStateWriteSuccess;
|
||||
break;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <nfc/protocols/felica/felica.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||
#include <nfc/protocols/mf_plus/mf_plus.h>
|
||||
#include <nfc/protocols/mf_desfire/mf_desfire.h>
|
||||
#include <nfc/protocols/slix/slix_device_defs.h>
|
||||
#include <nfc/protocols/st25tb/st25tb.h>
|
||||
@@ -39,6 +40,7 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = {
|
||||
[NfcProtocolFelica] = &nfc_device_felica,
|
||||
[NfcProtocolMfUltralight] = &nfc_device_mf_ultralight,
|
||||
[NfcProtocolMfClassic] = &nfc_device_mf_classic,
|
||||
[NfcProtocolMfPlus] = &nfc_device_mf_plus,
|
||||
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
|
||||
[NfcProtocolSlix] = &nfc_device_slix,
|
||||
[NfcProtocolSt25tb] = &nfc_device_st25tb,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <nfc/protocols/felica/felica_poller_defs.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
|
||||
#include <nfc/protocols/mf_plus/mf_plus_poller_defs.h>
|
||||
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
||||
#include <nfc/protocols/slix/slix_poller_defs.h>
|
||||
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
|
||||
@@ -21,6 +22,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = {
|
||||
[NfcProtocolFelica] = &nfc_poller_felica,
|
||||
[NfcProtocolMfUltralight] = &mf_ultralight_poller,
|
||||
[NfcProtocolMfClassic] = &mf_classic_poller,
|
||||
[NfcProtocolMfPlus] = &mf_plus_poller,
|
||||
[NfcProtocolMfDesfire] = &mf_desfire_poller,
|
||||
[NfcProtocolSlix] = &nfc_poller_slix,
|
||||
/* Add new pollers here */
|
||||
|
||||
@@ -61,6 +61,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = {
|
||||
/** List of ISO14443-4A child protocols. */
|
||||
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
|
||||
NfcProtocolMfDesfire,
|
||||
NfcProtocolMfPlus,
|
||||
};
|
||||
|
||||
/** List of ISO115693-3 child protocols. */
|
||||
@@ -128,6 +129,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = {
|
||||
.children_num = 0,
|
||||
.children_protocol = NULL,
|
||||
},
|
||||
[NfcProtocolMfPlus] =
|
||||
{
|
||||
.parent_protocol = NfcProtocolIso14443_4a,
|
||||
.children_num = 0,
|
||||
.children_protocol = NULL,
|
||||
},
|
||||
[NfcProtocolMfDesfire] =
|
||||
{
|
||||
.parent_protocol = NfcProtocolIso14443_4a,
|
||||
|
||||
@@ -184,6 +184,7 @@ typedef enum {
|
||||
NfcProtocolFelica,
|
||||
NfcProtocolMfUltralight,
|
||||
NfcProtocolMfClassic,
|
||||
NfcProtocolMfPlus,
|
||||
NfcProtocolMfDesfire,
|
||||
NfcProtocolSlix,
|
||||
NfcProtocolSt25tb,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "pulse_reader.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "signal_reader.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include "const.h"
|
||||
@@ -529,7 +529,8 @@ static bool
|
||||
bin_raw_type = BinRAWTypeGap;
|
||||
//looking for the last occurrence of gap
|
||||
ind = instance->data_raw_ind - 1;
|
||||
while((ind > 0) && (DURATION_DIFF(abs(instance->data_raw[ind]), gap) > gap_delta)) {
|
||||
while((ind > 0) &&
|
||||
(DURATION_DIFF(abs(instance->data_raw[ind]), (int32_t)gap) > gap_delta)) {
|
||||
ind--;
|
||||
}
|
||||
gap_ind = ind;
|
||||
@@ -544,10 +545,10 @@ static bool
|
||||
uint16_t bit_count = 0;
|
||||
do {
|
||||
gap_ind--;
|
||||
data_temp = (int)(round((float)(instance->data_raw[gap_ind]) / instance->te));
|
||||
data_temp = (int)(roundf((float)(instance->data_raw[gap_ind]) / instance->te));
|
||||
bin_raw_debug("%d ", data_temp);
|
||||
if(data_temp == 0) bit_count++; //there is noise in the package
|
||||
for(size_t i = 0; i < abs(data_temp); i++) {
|
||||
for(size_t i = 0; i < (size_t)abs(data_temp); i++) {
|
||||
bit_count++;
|
||||
if(ind) {
|
||||
ind--;
|
||||
@@ -563,7 +564,7 @@ static bool
|
||||
}
|
||||
}
|
||||
//split into full bytes if gap is caught
|
||||
if(DURATION_DIFF(abs(instance->data_raw[gap_ind]), gap) < gap_delta) {
|
||||
if(DURATION_DIFF(abs(instance->data_raw[gap_ind]), (int32_t)gap) < gap_delta) {
|
||||
instance->data_markup[data_markup_ind].byte_bias = ind >> 3;
|
||||
instance->data_markup[data_markup_ind++].bit_count = bit_count;
|
||||
bit_count = 0;
|
||||
@@ -807,11 +808,11 @@ static bool
|
||||
bin_raw_debug_tag(TAG, "Sequence analysis without gap\r\n");
|
||||
ind = 0;
|
||||
for(size_t i = 0; i < instance->data_raw_ind; i++) {
|
||||
int data_temp = (int)(round((float)(instance->data_raw[i]) / instance->te));
|
||||
int data_temp = (int)(roundf((float)(instance->data_raw[i]) / instance->te));
|
||||
if(data_temp == 0) break; //found an interval 2 times shorter than TE, this is noise
|
||||
bin_raw_debug("%d ", data_temp);
|
||||
|
||||
for(size_t k = 0; k < abs(data_temp); k++) {
|
||||
for(size_t k = 0; k < (size_t)abs(data_temp); k++) {
|
||||
if(data_temp > 0) {
|
||||
subghz_protocol_blocks_set_bit_array(
|
||||
true, instance->data, ind++, BIN_RAW_BUF_DATA_SIZE);
|
||||
|
||||
@@ -470,6 +470,6 @@ void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString
|
||||
output,
|
||||
"Ch:" CH_PATTERN " Dimm:%d%%\r\n",
|
||||
CNT_TO_CH(instance->generic.cnt),
|
||||
(int)(6.67 * (float)instance->generic.btn));
|
||||
(int)(6.67f * (float)instance->generic.btn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
instance->guard_time = round((float)duration / instance->te);
|
||||
instance->guard_time = roundf((float)duration / instance->te);
|
||||
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "protocol_items.h"
|
||||
#include "protocol_items.h" // IWYU pragma: keep
|
||||
|
||||
const SubGhzProtocol* subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_gate_tx,
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
#include "../subghz_file_encoder_worker.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "receiver.h"
|
||||
|
||||
#include "registry.h"
|
||||
#include "protocols/protocol_items.h"
|
||||
|
||||
#include <m-array.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "subghz_setting.h"
|
||||
#include "types.h"
|
||||
//#include "subghz_i.h"
|
||||
#include "types.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <m-list.h>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "protocols/base.h"
|
||||
#include "registry.h"
|
||||
#include "protocols/protocol_items.h"
|
||||
|
||||
struct SubGhzTransmitter {
|
||||
const SubGhzProtocol* protocol;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
|
||||
#define SUBGHZ_APP_FOLDER EXT_PATH("subghz")
|
||||
#define SUBGHZ_RAW_FOLDER EXT_PATH("subghz")
|
||||
#define SUBGHZ_APP_FILENAME_PREFIX "SubGHz"
|
||||
#define SUBGHZ_APP_FILENAME_EXTENSION ".sub"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "manchester_encoder.h"
|
||||
#include <stdio.h>
|
||||
#include <furi.h>
|
||||
|
||||
void manchester_encoder_reset(ManchesterEncoderState* state) {
|
||||
|
||||
@@ -224,6 +224,11 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header,
|
||||
|
||||
FuriString* full_extracted_fname;
|
||||
if(header->type == MTAR_TDIR) {
|
||||
// Skip "/" entry since concat would leave it dangling, also want caller to mkdir destination
|
||||
if(strcmp(header->name, "/") == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
full_extracted_fname = furi_string_alloc();
|
||||
path_concat(op_params->work_dir, header->name, full_extracted_fname);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user