mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-10-10 16:52:32 +02:00
* FeliCa: Handle non-hardware Polling commands NFC TagInfo and possibly other readers rely on Polling commands with Request Code of 1 (default System Code request) or non-FFFF System Code to detect card type. Since the NFC controller doesn't seem to handle them in hardware and simply bubbles them up, and then the Flipper firmware will just ignore them and refuse to respond afterwards, this causes the reading operation to fail. This commit adds a simple handler for such Polling commands so that readers behaving like NFC TagInfo could read the emulated card without failing. * Only handle cases when System Code is not FFFF The NFC controller should handle Polling commands with the System Code set to FFFF, so it's not necessary for the firmware to handle it. * Remove system code logging * More cleanups * Remove the claim that we need a poller change We already have enough information to determine whether or not the card supports NDEF since SYS_OP register value is included in all current Lite-S card dumps. * Respond to 12FC polling command when needed * Handle Polling with NDEF and Lite-S Service Code This allows the reader to specifically select the service by naming the Service Code. * Introduce API for manual handling of Polling commands Introduce nfc_felica_listener_timer_anticol_start() and nfc_felica_listener_timer_anticol_stop(). These are for now just wrappers around the block_tx timer that can be used to delay the response until the desired Time Slot. Thanks to the loose timing constraints of FeliCa collision resolution protocol, no compensation seems to be necessary. Also enabled the block_tx timer for FeliCa listener, but with both compensation and fdt set to 0 to keep the original behavior of not using the timer during normal data exchange. This API is now being used for handling Polling commands that are not handled by the NFC controller on the hardware side. * Document target_time_slot * Implement changes suggested by @RebornedBrain * api: f18 version sync * nfc: added stubs for `nfc_felica_listener_timer_anticol` for unit tests --------- Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: hedger <hedger@nanode.su>
532 lines
16 KiB
C
532 lines
16 KiB
C
#ifdef FW_CFG_unit_tests
|
|
|
|
#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>
|
|
|
|
#define NFC_MAX_BUFFER_SIZE (256)
|
|
|
|
typedef enum {
|
|
NfcTransportLogLevelWarning,
|
|
NfcTransportLogLevelInfo,
|
|
} NfcTransportLogLevel;
|
|
|
|
FuriMessageQueue* poller_queue = NULL;
|
|
FuriMessageQueue* listener_queue = NULL;
|
|
|
|
typedef enum {
|
|
NfcMessageTypeTx,
|
|
NfcMessageTypeTimeout,
|
|
NfcMessageTypeAbort,
|
|
} NfcMessageType;
|
|
|
|
typedef struct {
|
|
uint16_t data_bits;
|
|
uint8_t data[NFC_MAX_BUFFER_SIZE];
|
|
} NfcMessageData;
|
|
|
|
typedef struct {
|
|
NfcMessageType type;
|
|
NfcMessageData data;
|
|
} NfcMessage;
|
|
|
|
typedef enum {
|
|
NfcStateIdle,
|
|
NfcStateReady,
|
|
NfcStateReset,
|
|
} NfcState;
|
|
|
|
typedef enum {
|
|
Iso14443_3aColResStatusIdle,
|
|
Iso14443_3aColResStatusInProgress,
|
|
Iso14443_3aColResStatusDone,
|
|
} Iso14443_3aColResStatus;
|
|
|
|
typedef struct {
|
|
Iso14443_3aSensResp sens_resp;
|
|
Iso14443_3aSddResp sdd_resp[2];
|
|
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;
|
|
void* context;
|
|
|
|
NfcMode mode;
|
|
|
|
FuriThread* worker_thread;
|
|
};
|
|
|
|
static void nfc_test_print(
|
|
NfcTransportLogLevel log_level,
|
|
const char* message,
|
|
uint8_t* buffer,
|
|
uint16_t bits) {
|
|
FuriString* str = furi_string_alloc();
|
|
size_t bytes = (bits + 7) / 8;
|
|
|
|
for(size_t i = 0; i < bytes; i++) {
|
|
furi_string_cat_printf(str, " %02X", buffer[i]);
|
|
}
|
|
if(log_level == NfcTransportLogLevelWarning) {
|
|
FURI_LOG_W(message, "%s", furi_string_get_cstr(str));
|
|
} else {
|
|
FURI_LOG_I(message, "%s", furi_string_get_cstr(str));
|
|
}
|
|
|
|
furi_string_free(str);
|
|
}
|
|
|
|
static void nfc_prepare_col_res_data(
|
|
Nfc* instance,
|
|
uint8_t* uid,
|
|
uint8_t uid_len,
|
|
uint8_t* atqa,
|
|
uint8_t sak) {
|
|
memcpy(instance->col_res_data.sens_resp.sens_resp, atqa, 2);
|
|
|
|
if(uid_len == 7) {
|
|
instance->col_res_data.sdd_resp[0].nfcid[0] = 0x88;
|
|
memcpy(&instance->col_res_data.sdd_resp[0].nfcid[1], uid, 3);
|
|
uint8_t bss = 0;
|
|
for(size_t i = 0; i < 4; i++) {
|
|
bss ^= instance->col_res_data.sdd_resp[0].nfcid[i];
|
|
}
|
|
instance->col_res_data.sdd_resp[0].bss = bss;
|
|
instance->col_res_data.sel_resp[0].sak = 0x04;
|
|
|
|
memcpy(instance->col_res_data.sdd_resp[1].nfcid, &uid[3], 4);
|
|
bss = 0;
|
|
for(size_t i = 0; i < 4; i++) {
|
|
bss ^= instance->col_res_data.sdd_resp[1].nfcid[i];
|
|
}
|
|
instance->col_res_data.sdd_resp[1].bss = bss;
|
|
instance->col_res_data.sel_resp[1].sak = sak;
|
|
|
|
} else {
|
|
furi_crash("Not supporting not 7 bytes");
|
|
}
|
|
}
|
|
|
|
Nfc* nfc_alloc(void) {
|
|
Nfc* instance = malloc(sizeof(Nfc));
|
|
|
|
return instance;
|
|
}
|
|
|
|
void nfc_free(Nfc* instance) {
|
|
furi_check(instance);
|
|
|
|
free(instance);
|
|
}
|
|
|
|
void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) {
|
|
UNUSED(instance);
|
|
UNUSED(tech);
|
|
|
|
instance->mode = mode;
|
|
}
|
|
|
|
void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) {
|
|
UNUSED(instance);
|
|
UNUSED(fdt_poll_fc);
|
|
}
|
|
|
|
void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) {
|
|
UNUSED(instance);
|
|
UNUSED(fdt_listen_fc);
|
|
}
|
|
|
|
void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) {
|
|
UNUSED(instance);
|
|
UNUSED(mask_rx_time_fc);
|
|
}
|
|
|
|
void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) {
|
|
UNUSED(instance);
|
|
UNUSED(fdt_poll_poll_us);
|
|
}
|
|
|
|
void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) {
|
|
UNUSED(instance);
|
|
UNUSED(guard_time_us);
|
|
}
|
|
|
|
NfcError nfc_iso14443a_listener_set_col_res_data(
|
|
Nfc* instance,
|
|
uint8_t* uid,
|
|
uint8_t uid_len,
|
|
uint8_t* atqa,
|
|
uint8_t sak) {
|
|
furi_check(instance);
|
|
furi_check(uid);
|
|
furi_check(atqa);
|
|
|
|
nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak);
|
|
instance->software_col_res_required = true;
|
|
|
|
return NfcErrorNone;
|
|
}
|
|
|
|
static int32_t nfc_worker_poller(void* context) {
|
|
Nfc* instance = context;
|
|
furi_check(instance->callback);
|
|
|
|
instance->state = NfcStateReady;
|
|
NfcCommand command = NfcCommandContinue;
|
|
NfcEvent event = {};
|
|
|
|
while(true) {
|
|
event.type = NfcEventTypePollerReady;
|
|
command = instance->callback(event, instance->context);
|
|
if(command == NfcCommandStop) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
instance->state = NfcStateIdle;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) {
|
|
furi_check(instance->col_res_status != Iso14443_3aColResStatusDone);
|
|
BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
|
|
|
|
bool processed = false;
|
|
|
|
if((rx_bits == 7) && (rx_data[0] == 0x52)) {
|
|
instance->col_res_status = Iso14443_3aColResStatusInProgress;
|
|
bit_buffer_copy_bytes(
|
|
tx_buffer,
|
|
instance->col_res_data.sens_resp.sens_resp,
|
|
sizeof(instance->col_res_data.sens_resp.sens_resp));
|
|
nfc_listener_tx(instance, tx_buffer);
|
|
processed = true;
|
|
} else if(rx_bits == 2 * 8) {
|
|
if((rx_data[0] == 0x93) && (rx_data[1] == 0x20)) {
|
|
bit_buffer_copy_bytes(
|
|
tx_buffer,
|
|
(const uint8_t*)&instance->col_res_data.sdd_resp[0],
|
|
sizeof(Iso14443_3aSddResp));
|
|
nfc_listener_tx(instance, tx_buffer);
|
|
processed = true;
|
|
} else if((rx_data[0] == 0x95) && (rx_data[1] == 0x20)) {
|
|
bit_buffer_copy_bytes(
|
|
tx_buffer,
|
|
(const uint8_t*)&instance->col_res_data.sdd_resp[1],
|
|
sizeof(Iso14443_3aSddResp));
|
|
nfc_listener_tx(instance, tx_buffer);
|
|
processed = true;
|
|
}
|
|
} else if(rx_bits == 9 * 8) {
|
|
if((rx_data[0] == 0x93) && (rx_data[1] == 0x70)) {
|
|
bit_buffer_set_size_bytes(tx_buffer, 1);
|
|
bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[0].sak);
|
|
iso14443_crc_append(Iso14443CrcTypeA, tx_buffer);
|
|
nfc_listener_tx(instance, tx_buffer);
|
|
processed = true;
|
|
} else if((rx_data[0] == 0x95) && (rx_data[1] == 0x70)) {
|
|
bit_buffer_set_size_bytes(tx_buffer, 1);
|
|
bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[1].sak);
|
|
iso14443_crc_append(Iso14443CrcTypeA, 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;
|
|
}
|
|
} else if(rx_bits == 8 * 8) {
|
|
FelicaPollingRequest* request = (FelicaPollingRequest*)rx_data;
|
|
if(request->system_code == 0xFFFF ||
|
|
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;
|
|
}
|
|
}
|
|
|
|
if(!processed) {
|
|
NfcMessage message = {.type = NfcMessageTypeTimeout};
|
|
furi_message_queue_put(poller_queue, &message, FuriWaitForever);
|
|
}
|
|
|
|
bit_buffer_free(tx_buffer);
|
|
}
|
|
|
|
static int32_t nfc_worker_listener(void* context) {
|
|
Nfc* instance = context;
|
|
furi_check(instance->callback);
|
|
|
|
NfcMessage message = {};
|
|
|
|
NfcEventData event_data = {};
|
|
event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
|
|
NfcEvent nfc_event = {.data = event_data};
|
|
|
|
while(true) {
|
|
furi_message_queue_get(listener_queue, &message, FuriWaitForever);
|
|
bit_buffer_copy_bits(event_data.buffer, message.data.data, message.data.data_bits);
|
|
if((message.data.data[0] == 0x52) && (message.data.data_bits == 7)) {
|
|
instance->col_res_status = Iso14443_3aColResStatusIdle;
|
|
}
|
|
|
|
if(message.type == NfcMessageTypeAbort) {
|
|
break;
|
|
} else if(message.type == NfcMessageTypeTx) {
|
|
nfc_test_print(
|
|
NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits);
|
|
if(instance->software_col_res_required &&
|
|
(instance->col_res_status != Iso14443_3aColResStatusDone)) {
|
|
nfc_worker_listener_pass_col_res(
|
|
instance, message.data.data, message.data.data_bits);
|
|
} else {
|
|
instance->state = NfcStateReady;
|
|
nfc_event.type = NfcEventTypeRxEnd;
|
|
instance->callback(nfc_event, instance->context);
|
|
}
|
|
}
|
|
}
|
|
|
|
instance->state = NfcStateIdle;
|
|
instance->col_res_status = Iso14443_3aColResStatusIdle;
|
|
memset(&instance->col_res_data, 0, sizeof(instance->col_res_data));
|
|
bit_buffer_free(nfc_event.data.buffer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) {
|
|
furi_check(instance);
|
|
furi_check(instance->worker_thread == NULL);
|
|
|
|
if(instance->mode == NfcModeListener) {
|
|
furi_check(listener_queue == NULL);
|
|
// Check that poller didn't start
|
|
furi_check(poller_queue == NULL);
|
|
} else {
|
|
furi_check(poller_queue == NULL);
|
|
// Check that poller is started after listener
|
|
furi_check(listener_queue);
|
|
}
|
|
|
|
instance->callback = callback;
|
|
instance->context = context;
|
|
|
|
if(instance->mode == NfcModeListener) {
|
|
listener_queue = furi_message_queue_alloc(4, sizeof(NfcMessage));
|
|
} else {
|
|
poller_queue = furi_message_queue_alloc(4, sizeof(NfcMessage));
|
|
}
|
|
|
|
instance->worker_thread = furi_thread_alloc();
|
|
furi_thread_set_context(instance->worker_thread, instance);
|
|
furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHigh);
|
|
furi_thread_set_stack_size(instance->worker_thread, 8 * 1024);
|
|
|
|
if(instance->mode == NfcModeListener) {
|
|
furi_thread_set_name(instance->worker_thread, "NfcWorkerListener");
|
|
furi_thread_set_callback(instance->worker_thread, nfc_worker_listener);
|
|
} else {
|
|
furi_thread_set_name(instance->worker_thread, "NfcWorkerPoller");
|
|
furi_thread_set_callback(instance->worker_thread, nfc_worker_poller);
|
|
}
|
|
|
|
furi_thread_start(instance->worker_thread);
|
|
}
|
|
|
|
void nfc_stop(Nfc* instance) {
|
|
furi_check(instance);
|
|
furi_check(instance->worker_thread);
|
|
|
|
if(instance->mode == NfcModeListener) {
|
|
NfcMessage message = {.type = NfcMessageTypeAbort};
|
|
furi_message_queue_put(listener_queue, &message, FuriWaitForever);
|
|
furi_thread_join(instance->worker_thread);
|
|
|
|
furi_message_queue_free(listener_queue);
|
|
listener_queue = NULL;
|
|
|
|
furi_thread_free(instance->worker_thread);
|
|
instance->worker_thread = NULL;
|
|
} else {
|
|
furi_thread_join(instance->worker_thread);
|
|
|
|
furi_message_queue_free(poller_queue);
|
|
poller_queue = NULL;
|
|
|
|
furi_thread_free(instance->worker_thread);
|
|
instance->worker_thread = NULL;
|
|
}
|
|
}
|
|
|
|
// Called from worker thread
|
|
|
|
NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) {
|
|
furi_check(instance);
|
|
furi_check(poller_queue);
|
|
furi_check(listener_queue);
|
|
furi_check(tx_buffer);
|
|
|
|
NfcMessage message = {};
|
|
message.type = NfcMessageTypeTx;
|
|
message.data.data_bits = bit_buffer_get_size(tx_buffer);
|
|
bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer));
|
|
|
|
furi_message_queue_put(poller_queue, &message, FuriWaitForever);
|
|
|
|
return NfcErrorNone;
|
|
}
|
|
|
|
NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) {
|
|
return nfc_listener_tx(instance, tx_buffer);
|
|
}
|
|
|
|
NfcError
|
|
nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) {
|
|
furi_check(instance);
|
|
furi_check(tx_buffer);
|
|
furi_check(rx_buffer);
|
|
furi_check(poller_queue);
|
|
furi_check(listener_queue);
|
|
UNUSED(fwt);
|
|
|
|
NfcError error = NfcErrorNone;
|
|
|
|
NfcMessage message = {};
|
|
message.type = NfcMessageTypeTx;
|
|
message.data.data_bits = bit_buffer_get_size(tx_buffer);
|
|
bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer));
|
|
// Tx
|
|
furi_check(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk);
|
|
// Rx
|
|
FuriStatus status = furi_message_queue_get(poller_queue, &message, 50);
|
|
|
|
if(status == FuriStatusErrorTimeout) {
|
|
error = NfcErrorTimeout;
|
|
} else if(message.type == NfcMessageTypeTx) {
|
|
bit_buffer_copy_bits(rx_buffer, message.data.data, message.data.data_bits);
|
|
nfc_test_print(
|
|
NfcTransportLogLevelWarning, "TAG", message.data.data, message.data.data_bits);
|
|
} else if(message.type == NfcMessageTypeTimeout) {
|
|
error = NfcErrorTimeout;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
NfcError nfc_iso14443a_poller_trx_custom_parity(
|
|
Nfc* instance,
|
|
const BitBuffer* tx_buffer,
|
|
BitBuffer* rx_buffer,
|
|
uint32_t fwt) {
|
|
return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
|
|
}
|
|
|
|
// Technology specific API
|
|
|
|
NfcError nfc_iso14443a_poller_trx_short_frame(
|
|
Nfc* instance,
|
|
NfcIso14443aShortFrame frame,
|
|
BitBuffer* rx_buffer,
|
|
uint32_t fwt) {
|
|
UNUSED(frame);
|
|
|
|
BitBuffer* tx_buffer = bit_buffer_alloc(32);
|
|
bit_buffer_set_size(tx_buffer, 7);
|
|
bit_buffer_set_byte(tx_buffer, 0, 0x52);
|
|
|
|
NfcError error = nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
|
|
|
|
bit_buffer_free(tx_buffer);
|
|
|
|
return error;
|
|
}
|
|
|
|
NfcError nfc_iso14443a_poller_trx_sdd_frame(
|
|
Nfc* instance,
|
|
const BitBuffer* tx_buffer,
|
|
BitBuffer* rx_buffer,
|
|
uint32_t fwt) {
|
|
return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
|
|
}
|
|
|
|
NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
|
UNUSED(instance);
|
|
|
|
return NfcErrorNone;
|
|
}
|
|
|
|
NfcError nfc_felica_listener_set_sensf_res_data(
|
|
Nfc* instance,
|
|
const uint8_t* idm,
|
|
const uint8_t idm_len,
|
|
const uint8_t* pmm,
|
|
const uint8_t pmm_len,
|
|
const uint16_t sys_code) {
|
|
furi_assert(instance);
|
|
furi_assert(idm);
|
|
furi_assert(pmm);
|
|
furi_assert(idm_len == 8);
|
|
furi_assert(pmm_len == 8);
|
|
|
|
instance->pt_memory.system_code = sys_code;
|
|
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;
|
|
}
|
|
|
|
void nfc_felica_listener_timer_anticol_start(Nfc* instance, uint8_t target_time_slot) {
|
|
furi_check(instance);
|
|
|
|
UNUSED(target_time_slot);
|
|
}
|
|
|
|
void nfc_felica_listener_timer_anticol_stop(Nfc* instance) {
|
|
furi_check(instance);
|
|
}
|
|
|
|
#endif
|