mirror of
https://github.com/bettse/seader.git
synced 2026-03-30 14:15:56 +00:00
454 lines
17 KiB
C
454 lines
17 KiB
C
#include "seader_worker_i.h"
|
|
#include <furi_hal_gpio.h>
|
|
|
|
#include <flipper_format/flipper_format.h>
|
|
#include <lib/bit_lib/bit_lib.h>
|
|
|
|
#define TAG "SeaderWorker"
|
|
|
|
#define APDU_HEADER_LEN 5
|
|
#define ASN1_PREFIX 6
|
|
// #define ASN1_DEBUG true
|
|
|
|
#define RAW_DETECT_PIN &gpio_ext_pb3
|
|
|
|
#define RFAL_PICOPASS_TXRX_FLAGS \
|
|
(FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
|
|
FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP)
|
|
|
|
// Forward declaration
|
|
void seader_send_card_detected(SeaderUartBridge* seader_uart, CardDetails_t* cardDetails);
|
|
|
|
/***************************** Seader Worker API *******************************/
|
|
|
|
SeaderWorker* seader_worker_alloc() {
|
|
SeaderWorker* seader_worker = calloc(1, sizeof(SeaderWorker));
|
|
|
|
// Worker thread attributes
|
|
seader_worker->thread =
|
|
furi_thread_alloc_ex("SeaderWorker", 8192, seader_worker_task, seader_worker);
|
|
seader_worker->messages = furi_message_queue_alloc(3, sizeof(SeaderAPDU));
|
|
seader_worker->mq_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
|
|
seader_worker->callback = NULL;
|
|
seader_worker->context = NULL;
|
|
seader_worker->storage = furi_record_open(RECORD_STORAGE);
|
|
memset(seader_worker->sam_version, 0, sizeof(seader_worker->sam_version));
|
|
|
|
seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
|
|
|
|
furi_hal_gpio_init(RAW_DETECT_PIN, GpioModeInput, GpioPullUp, GpioSpeedLow);
|
|
// if pin is low, it is a raw SAM
|
|
if(furi_hal_gpio_read(RAW_DETECT_PIN) == false) {
|
|
FURI_LOG_I(TAG, "SAM Communication Type: RAW");
|
|
seader_worker->sam_comm_type = SeaderSamCommunicationTypeRaw;
|
|
} else {
|
|
FURI_LOG_I(TAG, "SAM Communication Type: SEC1210");
|
|
seader_worker->sam_comm_type = SeaderSamCommunicationTypeSec1210;
|
|
}
|
|
|
|
return seader_worker;
|
|
}
|
|
|
|
void seader_worker_free(SeaderWorker* seader_worker) {
|
|
furi_assert(seader_worker);
|
|
|
|
furi_thread_free(seader_worker->thread);
|
|
furi_message_queue_free(seader_worker->messages);
|
|
furi_mutex_free(seader_worker->mq_mutex);
|
|
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
free(seader_worker);
|
|
}
|
|
|
|
SeaderWorkerState seader_worker_get_state(SeaderWorker* seader_worker) {
|
|
return seader_worker->state;
|
|
}
|
|
|
|
void seader_worker_start(
|
|
SeaderWorker* seader_worker,
|
|
SeaderWorkerState state,
|
|
SeaderUartBridge* uart,
|
|
SeaderWorkerCallback callback,
|
|
void* context) {
|
|
furi_assert(seader_worker);
|
|
furi_assert(uart);
|
|
|
|
seader_worker->stage = SeaderPollerEventTypeCardDetect;
|
|
seader_worker->callback = callback;
|
|
seader_worker->context = context;
|
|
seader_worker->uart = uart;
|
|
seader_worker_change_state(seader_worker, state);
|
|
furi_thread_start(seader_worker->thread);
|
|
}
|
|
|
|
void seader_worker_stop(SeaderWorker* seader_worker) {
|
|
furi_assert(seader_worker);
|
|
if(seader_worker->state == SeaderWorkerStateBroken ||
|
|
seader_worker->state == SeaderWorkerStateReady) {
|
|
return;
|
|
}
|
|
|
|
seader_worker_change_state(seader_worker, SeaderWorkerStateStop);
|
|
furi_thread_join(seader_worker->thread);
|
|
}
|
|
|
|
void seader_worker_change_state(SeaderWorker* seader_worker, SeaderWorkerState state) {
|
|
seader_worker->state = state;
|
|
}
|
|
|
|
/***************************** Seader Worker Thread *******************************/
|
|
|
|
bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len) {
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
|
|
if(seader_process_success_response_i(seader, apdu, len, false, NULL)) {
|
|
// no-op, message was processed
|
|
} else {
|
|
FURI_LOG_I(TAG, "Enqueue SAM message, %d bytes", len);
|
|
uint32_t space = furi_message_queue_get_space(seader_worker->messages);
|
|
if(space > 0) {
|
|
SeaderAPDU seaderApdu = {};
|
|
seaderApdu.len = len;
|
|
memcpy(seaderApdu.buf, apdu, len);
|
|
|
|
if(furi_mutex_acquire(seader_worker->mq_mutex, FuriWaitForever) == FuriStatusOk) {
|
|
furi_message_queue_put(seader_worker->messages, &seaderApdu, FuriWaitForever);
|
|
furi_mutex_release(seader_worker->mq_mutex);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t len) {
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
SeaderUartBridge* seader_uart = seader_worker->uart;
|
|
if(len < 2) {
|
|
return false;
|
|
}
|
|
|
|
if(seader_worker->state == SeaderWorkerStateAPDURunner) {
|
|
return seader_apdu_runner_response(seader, apdu, len);
|
|
}
|
|
|
|
char* display = malloc(len * 2 + 1);
|
|
memset(display, 0, len * 2 + 1);
|
|
for(uint8_t i = 0; i < len; i++) {
|
|
snprintf(display + (i * 2), sizeof(display), "%02x", apdu[i]);
|
|
}
|
|
FURI_LOG_I(TAG, "APDU: %s", display);
|
|
free(display);
|
|
|
|
uint8_t SW1 = apdu[len - 2];
|
|
uint8_t SW2 = apdu[len - 1];
|
|
uint8_t GET_RESPONSE[] = {0x00, 0xc0, 0x00, 0x00, 0xff};
|
|
|
|
switch(SW1) {
|
|
case 0x61:
|
|
// FURI_LOG_I(TAG, "Request %d bytes", SW2);
|
|
GET_RESPONSE[4] = SW2;
|
|
// Only used for T=0
|
|
seader_ccid_XfrBlock(seader_uart, GET_RESPONSE, sizeof(GET_RESPONSE));
|
|
return true;
|
|
break;
|
|
case 0x90:
|
|
if(SW2 == 0x00) {
|
|
if(len > 2) {
|
|
return seader_process_success_response(seader, apdu, len - 2);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
FURI_LOG_W(TAG, "Unknown SW %02x%02x", SW1, SW2);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void seader_worker_virtual_credential(Seader* seader) {
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
|
|
// Detect card
|
|
seader_worker_card_detect(
|
|
seader, 0, NULL, seader->credential->diversifier, sizeof(PicopassSerialNum), NULL, 0);
|
|
|
|
bool running = true;
|
|
// Max times the loop will run with no message to process
|
|
uint8_t dead_loops = 20;
|
|
|
|
while(running) {
|
|
if(furi_mutex_acquire(seader_worker->mq_mutex, 0) == FuriStatusOk) {
|
|
uint32_t count = furi_message_queue_get_count(seader_worker->messages);
|
|
if(count > 0) {
|
|
FURI_LOG_I(TAG, "Dequeue SAM message [%ld messages]", count);
|
|
|
|
SeaderAPDU seaderApdu = {};
|
|
FuriStatus status =
|
|
furi_message_queue_get(seader_worker->messages, &seaderApdu, FuriWaitForever);
|
|
if(status != FuriStatusOk) {
|
|
FURI_LOG_W(TAG, "furi_message_queue_get fail %d", status);
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventWorkerExit);
|
|
}
|
|
if(seader_process_success_response_i(
|
|
seader, seaderApdu.buf, seaderApdu.len, true, NULL)) {
|
|
// no-op
|
|
} else {
|
|
FURI_LOG_I(TAG, "Response false");
|
|
running = false;
|
|
}
|
|
}
|
|
furi_mutex_release(seader_worker->mq_mutex);
|
|
} else {
|
|
dead_loops--;
|
|
running = (dead_loops > 0);
|
|
FURI_LOG_D(
|
|
TAG, "Dead loops: %d -> Running: %s", dead_loops, running ? "true" : "false");
|
|
}
|
|
running = (seader_worker->stage != SeaderPollerEventTypeComplete);
|
|
}
|
|
|
|
if(dead_loops > 0) {
|
|
FURI_LOG_D(TAG, "Final dead loops: %d", dead_loops);
|
|
} else {
|
|
view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit);
|
|
}
|
|
}
|
|
|
|
int32_t seader_worker_task(void* context) {
|
|
SeaderWorker* seader_worker = context;
|
|
Seader* seader = seader_worker->context;
|
|
SeaderUartBridge* seader_uart = seader_worker->uart;
|
|
|
|
if(seader_worker->state == SeaderWorkerStateCheckSam) {
|
|
FURI_LOG_D(TAG, "Check for SAM");
|
|
if(seader_worker->sam_comm_type == SeaderSamCommunicationTypeSec1210) {
|
|
seader_ccid_check_for_sam(seader_uart);
|
|
} else {
|
|
seader_uart_sam_reset(seader);
|
|
}
|
|
} else if(seader_worker->state == SeaderWorkerStateVirtualCredential) {
|
|
FURI_LOG_D(TAG, "Virtual Credential");
|
|
seader_worker_virtual_credential(seader);
|
|
} else if(seader_worker->state == SeaderWorkerStateAPDURunner) {
|
|
FURI_LOG_D(TAG, "APDU Runner");
|
|
seader_apdu_runner_init(seader);
|
|
return 0;
|
|
}
|
|
seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* spc) {
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
|
|
if(furi_mutex_acquire(seader_worker->mq_mutex, 0) == FuriStatusOk) {
|
|
furi_thread_set_current_priority(FuriThreadPriorityHighest);
|
|
uint32_t count = furi_message_queue_get_count(seader_worker->messages);
|
|
if(count > 0) {
|
|
FURI_LOG_I(TAG, "Dequeue SAM message [%ld messages]", count);
|
|
|
|
SeaderAPDU seaderApdu = {};
|
|
FuriStatus status =
|
|
furi_message_queue_get(seader_worker->messages, &seaderApdu, FuriWaitForever);
|
|
if(status != FuriStatusOk) {
|
|
FURI_LOG_W(TAG, "furi_message_queue_get fail %d", status);
|
|
seader_worker->stage = SeaderPollerEventTypeComplete;
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventWorkerExit);
|
|
}
|
|
|
|
if(seader_process_success_response_i(
|
|
seader, seaderApdu.buf, seaderApdu.len, true, spc)) {
|
|
// no-op
|
|
} else {
|
|
FURI_LOG_I(TAG, "Response false");
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventWorkerExit);
|
|
seader_worker->stage = SeaderPollerEventTypeComplete;
|
|
}
|
|
}
|
|
furi_mutex_release(seader_worker->mq_mutex);
|
|
} else {
|
|
furi_thread_set_current_priority(FuriThreadPriorityLowest);
|
|
}
|
|
}
|
|
|
|
NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
|
|
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
|
NfcCommand ret = NfcCommandContinue;
|
|
|
|
Seader* seader = context;
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
|
|
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
|
SeaderPollerContainer spc = {.iso14443_4a_poller = event.instance};
|
|
|
|
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
|
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventPollerDetect);
|
|
|
|
nfc_device_set_data(
|
|
seader->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(seader->poller));
|
|
|
|
size_t uid_len;
|
|
const uint8_t* uid = nfc_device_get_uid(seader->nfc_device, &uid_len);
|
|
|
|
const Iso14443_4aData* iso14443_4a_data =
|
|
nfc_device_get_data(seader->nfc_device, NfcProtocolIso14443_4a);
|
|
const Iso14443_3aData* iso14443_3a_data = iso14443_4a_get_base_data(iso14443_4a_data);
|
|
|
|
uint32_t t1_tk_size = 0;
|
|
if(iso14443_4a_data->ats_data.t1_tk != NULL) {
|
|
t1_tk_size = simple_array_get_count(iso14443_4a_data->ats_data.t1_tk);
|
|
if(t1_tk_size > 0xFF) {
|
|
t1_tk_size = 0;
|
|
}
|
|
}
|
|
|
|
uint8_t ats_len = 0;
|
|
uint8_t* ats = malloc(4 + t1_tk_size);
|
|
furi_assert(ats);
|
|
|
|
if(iso14443_4a_data->ats_data.tl > 1) {
|
|
ats[ats_len++] = iso14443_4a_data->ats_data.t0;
|
|
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1) {
|
|
ats[ats_len++] = iso14443_4a_data->ats_data.ta_1;
|
|
}
|
|
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1) {
|
|
ats[ats_len++] = iso14443_4a_data->ats_data.tb_1;
|
|
}
|
|
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TC1) {
|
|
ats[ats_len++] = iso14443_4a_data->ats_data.tc_1;
|
|
}
|
|
|
|
if(t1_tk_size != 0) {
|
|
memcpy(
|
|
ats + ats_len,
|
|
simple_array_cget_data(iso14443_4a_data->ats_data.t1_tk),
|
|
t1_tk_size);
|
|
ats_len += t1_tk_size;
|
|
}
|
|
}
|
|
|
|
uint8_t sak = iso14443_3a_get_sak(iso14443_3a_data);
|
|
|
|
seader_worker_card_detect(
|
|
seader, sak, (uint8_t*)iso14443_3a_data->atqa, uid, uid_len, ats, ats_len);
|
|
|
|
free(ats);
|
|
|
|
// nfc_set_fdt_poll_fc(event.instance, SEADER_POLLER_MAX_FWT);
|
|
furi_thread_set_current_priority(FuriThreadPriorityLowest);
|
|
seader_worker->stage = SeaderPollerEventTypeConversation;
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
|
|
seader_worker_poller_conversation(seader, &spc);
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
|
|
ret = NfcCommandStop;
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
|
|
ret = NfcCommandStop;
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventWorkerExit);
|
|
FURI_LOG_W(TAG, "SeaderPollerEventTypeFail");
|
|
}
|
|
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
|
|
Iso14443_4aPollerEventData* data = iso14443_4a_event->data;
|
|
Iso14443_4aError error = data->error;
|
|
FURI_LOG_W(TAG, "Iso14443_4aError %i", error);
|
|
// I was hoping to catch MFC here, but it seems to be treated the same (None) as no card being present.
|
|
switch(error) {
|
|
case Iso14443_4aErrorNone:
|
|
break;
|
|
case Iso14443_4aErrorNotPresent:
|
|
break;
|
|
case Iso14443_4aErrorProtocol:
|
|
ret = NfcCommandStop;
|
|
break;
|
|
case Iso14443_4aErrorTimeout:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* context) {
|
|
furi_assert(event.protocol == NfcProtocolMfClassic);
|
|
NfcCommand ret = NfcCommandContinue;
|
|
|
|
Seader* seader = context;
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
|
|
MfClassicPollerEvent* mfc_event = event.event_data;
|
|
SeaderPollerContainer spc = {.mfc_poller = event.instance};
|
|
|
|
if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
|
|
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventPollerDetect);
|
|
|
|
const MfClassicData* mfc_data = nfc_poller_get_data(seader->poller);
|
|
uint8_t sak = iso14443_3a_get_sak(mfc_data->iso14443_3a_data);
|
|
size_t uid_len = 0;
|
|
const uint8_t* uid = mf_classic_get_uid(mfc_data, &uid_len);
|
|
seader_worker_card_detect(seader, sak, NULL, uid, uid_len, NULL, 0);
|
|
furi_thread_set_current_priority(FuriThreadPriorityLowest);
|
|
seader_worker->stage = SeaderPollerEventTypeConversation;
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
|
|
seader_worker_poller_conversation(seader, &spc);
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
|
|
ret = NfcCommandStop;
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
|
|
ret = NfcCommandStop;
|
|
}
|
|
} else if(mfc_event->type == MfClassicPollerEventTypeFail) {
|
|
ret = NfcCommandStop;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
NfcCommand seader_worker_poller_callback_picopass(PicopassPollerEvent event, void* context) {
|
|
furi_assert(context);
|
|
NfcCommand ret = NfcCommandContinue;
|
|
|
|
Seader* seader = context;
|
|
SeaderWorker* seader_worker = seader->worker;
|
|
// I know this is is passing the same thing that is on seader all the way down, but I prefer the symmetry between the 15a and iso15 stuff
|
|
PicopassPoller* instance = seader->picopass_poller;
|
|
SeaderPollerContainer spc = {.picopass_poller = instance};
|
|
|
|
if(event.type == PicopassPollerEventTypeCardDetected) {
|
|
seader_worker->stage = SeaderPollerEventTypeCardDetect;
|
|
} else if(event.type == PicopassPollerEventTypeSuccess) {
|
|
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventPollerDetect);
|
|
uint8_t* csn = picopass_poller_get_csn(instance);
|
|
seader_worker_card_detect(seader, 0, NULL, csn, sizeof(PicopassSerialNum), NULL, 0);
|
|
furi_thread_set_current_priority(FuriThreadPriorityLowest);
|
|
seader_worker->stage = SeaderPollerEventTypeConversation;
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
|
|
seader_worker_poller_conversation(seader, &spc);
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
|
|
ret = NfcCommandStop;
|
|
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
|
|
view_dispatcher_send_custom_event(
|
|
seader->view_dispatcher, SeaderCustomEventWorkerExit);
|
|
ret = NfcCommandStop;
|
|
}
|
|
} else if(event.type == PicopassPollerEventTypeFail) {
|
|
ret = NfcCommandStop;
|
|
FURI_LOG_W(TAG, "PicopassPollerEventTypeFail");
|
|
} else {
|
|
FURI_LOG_D(TAG, "picopass event type %x", event.type);
|
|
}
|
|
|
|
return ret;
|
|
}
|