mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-17 20:19:43 -07:00
NFC: Implement Type 4 Tag rudimentary emulation
This commit is contained in:
@@ -2,13 +2,13 @@
|
||||
#include "type_4_tag_render.h"
|
||||
|
||||
#include <nfc/protocols/type_4_tag/type_4_tag_poller.h>
|
||||
#include <nfc/protocols/type_4_tag/type_4_tag_listener.h>
|
||||
#include <toolbox/pretty_format.h>
|
||||
|
||||
#include "nfc/nfc_app_i.h"
|
||||
|
||||
#include "../nfc_protocol_support_common.h"
|
||||
#include "../nfc_protocol_support_gui_common.h"
|
||||
#include "../iso14443_4a/iso14443_4a_i.h"
|
||||
|
||||
enum {
|
||||
SubmenuIndexWrite = SubmenuIndexCommonMax,
|
||||
@@ -167,19 +167,42 @@ static void nfc_scene_read_success_on_enter_type_4_tag(NfcApp* instance) {
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static void nfc_scene_emulate_on_enter_type_4_tag(NfcApp* instance) {
|
||||
// TODO: Implement full emulation
|
||||
const Iso14443_4aData* iso14443_4a_data =
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);
|
||||
static NfcCommand
|
||||
nfc_scene_emulate_listener_callback_type_4_tag(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolType4Tag);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
instance->listener =
|
||||
nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data);
|
||||
NfcApp* nfc = context;
|
||||
Type4TagListenerEvent* type_4_tag_event = event.event_data;
|
||||
|
||||
if(type_4_tag_event->type == Type4TagListenerEventTypeCustomCommand) {
|
||||
if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
|
||||
furi_string_cat_printf(nfc->text_box_store, "R:");
|
||||
for(size_t i = 0; i < bit_buffer_get_size_bytes(type_4_tag_event->data->buffer); i++) {
|
||||
furi_string_cat_printf(
|
||||
nfc->text_box_store,
|
||||
" %02X",
|
||||
bit_buffer_get_byte(type_4_tag_event->data->buffer, i));
|
||||
}
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static void nfc_scene_emulate_on_enter_type_4_tag(NfcApp* instance) {
|
||||
const Type4TagData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag);
|
||||
|
||||
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolType4Tag, data);
|
||||
nfc_listener_start(
|
||||
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
|
||||
instance->listener, nfc_scene_emulate_listener_callback_type_4_tag, instance);
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_type_4_tag = {
|
||||
.features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo,
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_listener_defs.h>
|
||||
#include <nfc/protocols/slix/slix_listener_defs.h>
|
||||
#include <nfc/protocols/type_4_tag/type_4_tag_listener_defs.h>
|
||||
|
||||
const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = {
|
||||
[NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a,
|
||||
@@ -21,5 +22,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = {
|
||||
[NfcProtocolMfDesfire] = NULL,
|
||||
[NfcProtocolSlix] = &nfc_listener_slix,
|
||||
[NfcProtocolSt25tb] = NULL,
|
||||
[NfcProtocolType4Tag] = NULL,
|
||||
[NfcProtocolType4Tag] = &nfc_listener_type_4_tag,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ typedef enum {
|
||||
Type4TagErrorWrongFormat,
|
||||
Type4TagErrorNotSupported,
|
||||
Type4TagErrorUnknown,
|
||||
Type4TagErrorCustomCommand,
|
||||
} Type4TagError;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -7,6 +7,19 @@
|
||||
|
||||
#define TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE (16U)
|
||||
|
||||
Type4TagError type_4_tag_process_error(Iso14443_4aError error) {
|
||||
switch(error) {
|
||||
case Iso14443_4aErrorNone:
|
||||
return Type4TagErrorNone;
|
||||
case Iso14443_4aErrorNotPresent:
|
||||
return Type4TagErrorNotPresent;
|
||||
case Iso14443_4aErrorTimeout:
|
||||
return Type4TagErrorTimeout;
|
||||
default:
|
||||
return Type4TagErrorProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff) {
|
||||
uint32_t ndef_data_size;
|
||||
if(!flipper_format_read_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) {
|
||||
|
||||
@@ -3,27 +3,37 @@
|
||||
#include "type_4_tag.h"
|
||||
|
||||
// ISO SELECT FILE command and parameters
|
||||
#define TYPE_4_TAG_ISO_SELECT_CMD 0x00, 0xA4
|
||||
#define TYPE_4_TAG_ISO_SELECT_P1_BY_NAME (0x04)
|
||||
#define TYPE_4_TAG_ISO_SELECT_P1_BY_ID (0x00)
|
||||
#define TYPE_4_TAG_ISO_SELECT_P2_EMPTY (0x0C)
|
||||
#define TYPE_4_TAG_ISO_SELECT_LE_EMPTY (0x00)
|
||||
#define TYPE_4_TAG_ISO_SELECT_CMD 0x00, 0xA4
|
||||
#define TYPE_4_TAG_ISO_SELECT_P1_BY_DF_NAME (0x04)
|
||||
#define TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID (0x02)
|
||||
#define TYPE_4_TAG_ISO_SELECT_P1_BY_ID (0x00)
|
||||
#define TYPE_4_TAG_ISO_SELECT_P2_EMPTY (0x0C)
|
||||
#define TYPE_4_TAG_ISO_SELECT_LE_EMPTY (0x00)
|
||||
|
||||
// ISO READ BINARY command and parameters
|
||||
#define TYPE_4_TAG_ISO_READ_CMD 0x00, 0xB0
|
||||
#define TYPE_4_TAG_ISO_READ_P1_EMPTY (0x00)
|
||||
#define TYPE_4_TAG_ISO_READ_P1_ID_MASK (1 << 7)
|
||||
#define TYPE_4_TAG_ISO_READ_P2_BEGINNING (0x00)
|
||||
#define TYPE_4_TAG_ISO_READ_LE_FULL (0x00)
|
||||
|
||||
// Common APDU parameters and values
|
||||
#define TYPE_4_TAG_ISO_STATUS_LEN (2U)
|
||||
#define TYPE_4_TAG_ISO_STATUS_SUCCESS 0x90, 0x00
|
||||
#define TYPE_4_TAG_ISO_RW_CHUNK_LEN (255U)
|
||||
#define TYPE_4_TAG_ISO_APP_NAME_LEN (7U)
|
||||
#define TYPE_4_TAG_ISO_APP_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01
|
||||
#define TYPE_4_TAG_T4T_CC_FILE_ID_LEN (2U)
|
||||
#define TYPE_4_TAG_T4T_CC_FILE_ID 0xE1, 0x03
|
||||
#define TYPE_4_TAG_T4T_CC_VNO (0x20)
|
||||
#define TYPE_4_TAG_ISO_STATUS_LEN (2U)
|
||||
#define TYPE_4_TAG_ISO_STATUS_SUCCESS 0x90, 0x00
|
||||
#define TYPE_4_TAG_ISO_STATUS_OFFSET_ERR 0x6B, 0x00
|
||||
#define TYPE_4_TAG_ISO_STATUS_NOT_FOUND 0x6A, 0x82
|
||||
#define TYPE_4_TAG_ISO_STATUS_NO_SUPPORT 0x6A, 0x81
|
||||
#define TYPE_4_TAG_ISO_STATUS_BAD_PARAMS 0x6A, 0x86
|
||||
#define TYPE_4_TAG_ISO_STATUS_NO_CMD 0x68, 0x00
|
||||
#define TYPE_4_TAG_ISO_RW_CHUNK_LEN (255U)
|
||||
#define TYPE_4_TAG_ISO_APP_NAME_LEN (7U)
|
||||
#define TYPE_4_TAG_ISO_APP_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01
|
||||
#define TYPE_4_TAG_T4T_CC_FILE_ID_LEN (2U)
|
||||
#define TYPE_4_TAG_T4T_CC_FILE_ID 0xE1, 0x03
|
||||
#define TYPE_4_TAG_T4T_CC_VNO (0x20)
|
||||
|
||||
// Read returns 2 byte status trailer, write sends 5 byte command header
|
||||
#define TYPE_4_TAG_BUF_SIZE (TYPE_4_TAG_ISO_RW_CHUNK_LEN + 5)
|
||||
|
||||
// Capability Container parsing structures
|
||||
|
||||
@@ -57,6 +67,10 @@ typedef struct FURI_PACKED {
|
||||
Type4TagCcTlv tlv[];
|
||||
} Type4TagCc;
|
||||
|
||||
// Internal helpers
|
||||
|
||||
Type4TagError type_4_tag_process_error(Iso14443_4aError error);
|
||||
|
||||
// Load internal Type4Tag structures
|
||||
|
||||
bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "type_4_tag_listener_i.h"
|
||||
#include "type_4_tag_listener_defs.h"
|
||||
#include "type_4_tag_i.h"
|
||||
|
||||
#define TAG "Type4TagListener"
|
||||
|
||||
static void type_4_tag_listener_reset_state(Type4TagListener* instance) {
|
||||
instance->state = Type4TagListenerStateIdle;
|
||||
}
|
||||
|
||||
static Type4TagListener*
|
||||
type_4_tag_listener_alloc(Iso14443_4aListener* iso14443_4a_listener, Type4TagData* data) {
|
||||
furi_assert(iso14443_4a_listener);
|
||||
|
||||
Type4TagListener* instance = malloc(sizeof(Type4TagListener));
|
||||
instance->iso14443_4a_listener = iso14443_4a_listener;
|
||||
instance->data = data;
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE);
|
||||
|
||||
instance->type_4_tag_event.data = &instance->type_4_tag_event_data;
|
||||
instance->generic_event.protocol = NfcProtocolType4Tag;
|
||||
instance->generic_event.instance = instance;
|
||||
instance->generic_event.event_data = &instance->type_4_tag_event;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void type_4_tag_listener_free(Type4TagListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->tx_buffer);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void type_4_tag_listener_set_callback(
|
||||
Type4TagListener* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static const Type4TagData* type_4_tag_listener_get_data(Type4TagListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static NfcCommand type_4_tag_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolIso15693_3);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Type4TagListener* instance = context;
|
||||
Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data;
|
||||
BitBuffer* rx_buffer = iso14443_4a_event->data->buffer;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeFieldOff) {
|
||||
type_4_tag_listener_reset_state(instance);
|
||||
command = NfcCommandSleep;
|
||||
} else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeHalted) {
|
||||
type_4_tag_listener_reset_state(instance);
|
||||
} else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) {
|
||||
const Type4TagError error = type_4_tag_listener_handle_apdu(instance, rx_buffer);
|
||||
if(error == Type4TagErrorCustomCommand && instance->callback) {
|
||||
instance->type_4_tag_event.type = Type4TagListenerEventTypeCustomCommand;
|
||||
instance->type_4_tag_event.data->buffer = rx_buffer;
|
||||
command = instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
const NfcListenerBase nfc_listener_type_4_tag = {
|
||||
.alloc = (NfcListenerAlloc)type_4_tag_listener_alloc,
|
||||
.free = (NfcListenerFree)type_4_tag_listener_free,
|
||||
.set_callback = (NfcListenerSetCallback)type_4_tag_listener_set_callback,
|
||||
.get_data = (NfcListenerGetData)type_4_tag_listener_get_data,
|
||||
.run = (NfcListenerRun)type_4_tag_listener_run,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "type_4_tag.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Type4TagListener Type4TagListener;
|
||||
|
||||
typedef enum {
|
||||
Type4TagListenerEventTypeCustomCommand,
|
||||
} Type4TagListenerEventType;
|
||||
|
||||
typedef struct {
|
||||
BitBuffer* buffer;
|
||||
} Type4TagListenerEventData;
|
||||
|
||||
typedef struct {
|
||||
Type4TagListenerEventType type;
|
||||
Type4TagListenerEventData* data;
|
||||
} Type4TagListenerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
extern const NfcListenerBase nfc_listener_type_4_tag;
|
||||
@@ -0,0 +1,314 @@
|
||||
#include "type_4_tag_listener_i.h"
|
||||
#include "type_4_tag_i.h"
|
||||
|
||||
#include <bit_lib/bit_lib.h>
|
||||
|
||||
#define TAG "Type4TagListener"
|
||||
|
||||
typedef Type4TagError (*Type4TagListenerApduHandler)(
|
||||
Type4TagListener* instance,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
size_t lc,
|
||||
const uint8_t* data,
|
||||
size_t le);
|
||||
|
||||
typedef struct {
|
||||
uint8_t cla_ins[2];
|
||||
Type4TagListenerApduHandler handler;
|
||||
} Type4TagListenerApduCommand;
|
||||
|
||||
static const uint8_t type_4_tag_success_apdu[] = {TYPE_4_TAG_ISO_STATUS_SUCCESS};
|
||||
static const uint8_t type_4_tag_offset_error_apdu[] = {TYPE_4_TAG_ISO_STATUS_OFFSET_ERR};
|
||||
static const uint8_t type_4_tag_not_found_apdu[] = {TYPE_4_TAG_ISO_STATUS_NOT_FOUND};
|
||||
static const uint8_t type_4_tag_no_support_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_SUPPORT};
|
||||
static const uint8_t type_4_tag_bad_params_apdu[] = {TYPE_4_TAG_ISO_STATUS_BAD_PARAMS};
|
||||
static const uint8_t type_4_tag_no_cmd_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_CMD};
|
||||
|
||||
static Type4TagError type_4_tag_listener_iso_select(
|
||||
Type4TagListener* instance,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
size_t lc,
|
||||
const uint8_t* data,
|
||||
size_t le) {
|
||||
UNUSED(p2);
|
||||
UNUSED(le);
|
||||
|
||||
if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_DF_NAME) {
|
||||
static const uint8_t t4t_app[TYPE_4_TAG_ISO_APP_NAME_LEN] = {TYPE_4_TAG_ISO_APP_NAME};
|
||||
if(lc == sizeof(t4t_app) && memcmp(t4t_app, data, sizeof(t4t_app)) == 0) {
|
||||
instance->state = Type4TagListenerStateSelectedApplication;
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
|
||||
return Type4TagErrorNone;
|
||||
}
|
||||
|
||||
instance->state = Type4TagListenerStateIdle;
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu));
|
||||
return Type4TagErrorCustomCommand;
|
||||
}
|
||||
|
||||
if(instance->state >= Type4TagListenerStateSelectedApplication) {
|
||||
if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID || p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID) {
|
||||
static const uint8_t cc_id[TYPE_4_TAG_T4T_CC_FILE_ID_LEN] = {
|
||||
TYPE_4_TAG_T4T_CC_FILE_ID};
|
||||
if(lc == sizeof(cc_id) && memcmp(cc_id, data, sizeof(cc_id)) == 0) {
|
||||
instance->state = Type4TagListenerStateSelectedCapabilityContainer;
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
|
||||
return Type4TagErrorNone;
|
||||
}
|
||||
|
||||
uint8_t ndef_file_id_be[sizeof(instance->data->ndef_file_id)];
|
||||
bit_lib_num_to_bytes_be(
|
||||
instance->data->ndef_file_id, sizeof(ndef_file_id_be), ndef_file_id_be);
|
||||
if(lc == sizeof(ndef_file_id_be) &&
|
||||
memcmp(ndef_file_id_be, data, sizeof(ndef_file_id_be)) == 0) {
|
||||
instance->state = Type4TagListenerStateSelectedNdefMessage;
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
|
||||
return Type4TagErrorNone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->state = Type4TagListenerStateIdle;
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu));
|
||||
return Type4TagErrorCustomCommand;
|
||||
}
|
||||
|
||||
static Type4TagError type_4_tag_listener_iso_read(
|
||||
Type4TagListener* instance,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
size_t lc,
|
||||
const uint8_t* data,
|
||||
size_t le) {
|
||||
UNUSED(lc);
|
||||
UNUSED(data);
|
||||
|
||||
size_t offset;
|
||||
if(p1 & TYPE_4_TAG_ISO_READ_P1_ID_MASK) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu));
|
||||
return Type4TagErrorCustomCommand;
|
||||
} else {
|
||||
offset = (p1 << 8) + p2;
|
||||
}
|
||||
|
||||
if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) {
|
||||
uint8_t cc_buf[sizeof(Type4TagCc) + sizeof(Type4TagCcTlv)];
|
||||
if(offset >= sizeof(cc_buf)) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer,
|
||||
type_4_tag_offset_error_apdu,
|
||||
sizeof(type_4_tag_offset_error_apdu));
|
||||
return Type4TagErrorWrongFormat;
|
||||
}
|
||||
Type4TagCc* cc = (Type4TagCc*)cc_buf;
|
||||
bit_lib_num_to_bytes_be(sizeof(cc_buf), sizeof(cc->len), (void*)&cc->len);
|
||||
cc->t4t_vno = TYPE_4_TAG_T4T_CC_VNO;
|
||||
bit_lib_num_to_bytes_be(
|
||||
instance->data->is_tag_specific ? instance->data->chunk_max_read :
|
||||
TYPE_4_TAG_ISO_RW_CHUNK_LEN,
|
||||
sizeof(cc->mle),
|
||||
(void*)&cc->mle);
|
||||
bit_lib_num_to_bytes_be(
|
||||
instance->data->is_tag_specific ? instance->data->chunk_max_write :
|
||||
TYPE_4_TAG_ISO_RW_CHUNK_LEN,
|
||||
sizeof(cc->mlc),
|
||||
(void*)&cc->mlc);
|
||||
cc->tlv[0].type = Type4TagCcTlvTypeNdefFileCtrl;
|
||||
cc->tlv[0].len = sizeof(cc->tlv[0].value.ndef_file_ctrl);
|
||||
bit_lib_num_to_bytes_be(
|
||||
instance->data->ndef_file_id,
|
||||
sizeof(cc->tlv[0].value.ndef_file_ctrl.file_id),
|
||||
(void*)&cc->tlv[0].value.ndef_file_ctrl.file_id);
|
||||
bit_lib_num_to_bytes_be(
|
||||
instance->data->is_tag_specific ? instance->data->ndef_max_len :
|
||||
TYPE_4_TAG_ISO_RW_CHUNK_LEN,
|
||||
sizeof(cc->tlv[0].value.ndef_file_ctrl.max_len),
|
||||
(void*)&cc->tlv[0].value.ndef_file_ctrl.max_len);
|
||||
cc->tlv[0].value.ndef_file_ctrl.read_perm =
|
||||
instance->data->is_tag_specific ? instance->data->ndef_read_lock : 0x00;
|
||||
cc->tlv[0].value.ndef_file_ctrl.write_perm =
|
||||
instance->data->is_tag_specific ? instance->data->ndef_write_lock : 0x00;
|
||||
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, cc_buf + offset, MIN(sizeof(cc_buf) - offset, le));
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
|
||||
return Type4TagErrorNone;
|
||||
}
|
||||
|
||||
if(instance->state == Type4TagListenerStateSelectedNdefMessage) {
|
||||
size_t ndef_file_len =
|
||||
instance->data->ndef_data ? simple_array_get_count(instance->data->ndef_data) : 0;
|
||||
bool included_len = false;
|
||||
if(offset < sizeof(uint16_t)) {
|
||||
uint8_t ndef_file_len_be[sizeof(uint16_t)];
|
||||
bit_lib_num_to_bytes_be(ndef_file_len, sizeof(ndef_file_len_be), ndef_file_len_be);
|
||||
uint8_t read_len = MIN(sizeof(ndef_file_len_be) - offset, le);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, &ndef_file_len_be[offset], read_len);
|
||||
included_len = true;
|
||||
offset = sizeof(uint16_t);
|
||||
le -= read_len;
|
||||
}
|
||||
offset -= sizeof(uint16_t);
|
||||
|
||||
if(offset >= ndef_file_len) {
|
||||
if(included_len) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
|
||||
return Type4TagErrorNone;
|
||||
} else {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer,
|
||||
type_4_tag_offset_error_apdu,
|
||||
sizeof(type_4_tag_offset_error_apdu));
|
||||
return Type4TagErrorWrongFormat;
|
||||
}
|
||||
}
|
||||
const uint8_t* ndef_data = simple_array_cget_data(instance->data->ndef_data);
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, &ndef_data[offset], MIN(ndef_file_len - offset, le));
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu));
|
||||
return Type4TagErrorNone;
|
||||
}
|
||||
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu));
|
||||
return Type4TagErrorCustomCommand;
|
||||
}
|
||||
|
||||
static const Type4TagListenerApduCommand type_4_tag_listener_commands[] = {
|
||||
{
|
||||
.cla_ins = {TYPE_4_TAG_ISO_SELECT_CMD},
|
||||
.handler = type_4_tag_listener_iso_select,
|
||||
},
|
||||
{
|
||||
.cla_ins = {TYPE_4_TAG_ISO_READ_CMD},
|
||||
.handler = type_4_tag_listener_iso_read,
|
||||
},
|
||||
};
|
||||
|
||||
Type4TagError
|
||||
type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer) {
|
||||
Type4TagError error = Type4TagErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t cla_ins[2];
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t body[];
|
||||
} Type4TagApdu;
|
||||
|
||||
const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer);
|
||||
|
||||
if(buf_size < sizeof(Type4TagApdu)) {
|
||||
error = Type4TagErrorWrongFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const Type4TagApdu* apdu = (const Type4TagApdu*)bit_buffer_get_data(rx_buffer);
|
||||
|
||||
Type4TagListenerApduHandler handler = NULL;
|
||||
for(size_t i = 0; i < COUNT_OF(type_4_tag_listener_commands); i++) {
|
||||
const Type4TagListenerApduCommand* command = &type_4_tag_listener_commands[i];
|
||||
if(memcmp(apdu->cla_ins, command->cla_ins, sizeof(apdu->cla_ins)) == 0) {
|
||||
handler = command->handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!handler) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, type_4_tag_no_cmd_apdu, sizeof(type_4_tag_no_cmd_apdu));
|
||||
error = Type4TagErrorCustomCommand;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t body_size = buf_size - offsetof(Type4TagApdu, body);
|
||||
size_t lc;
|
||||
const uint8_t* data = apdu->body;
|
||||
size_t le;
|
||||
if(body_size == 0) {
|
||||
lc = 0;
|
||||
data = NULL;
|
||||
le = 0;
|
||||
} else if(body_size == 1) {
|
||||
lc = 0;
|
||||
data = NULL;
|
||||
le = apdu->body[0];
|
||||
if(le == 0) {
|
||||
le = 256;
|
||||
}
|
||||
} else if(body_size == 3 && apdu->body[0] == 0) {
|
||||
lc = 0;
|
||||
data = NULL;
|
||||
le = bit_lib_bytes_to_num_be(&apdu->body[1], sizeof(uint16_t));
|
||||
if(le == 0) {
|
||||
le = 65536;
|
||||
}
|
||||
} else {
|
||||
bool extended_lc = false;
|
||||
if(data[0] == 0) {
|
||||
extended_lc = true;
|
||||
lc = bit_lib_bytes_to_num_be(&data[1], sizeof(uint16_t));
|
||||
data += 1 + sizeof(uint16_t);
|
||||
body_size -= 1 + sizeof(uint16_t);
|
||||
} else {
|
||||
lc = data[0];
|
||||
data++;
|
||||
body_size--;
|
||||
}
|
||||
if(lc == 0 || body_size < lc) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer,
|
||||
type_4_tag_bad_params_apdu,
|
||||
sizeof(type_4_tag_bad_params_apdu));
|
||||
error = Type4TagErrorWrongFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
if(body_size == lc) {
|
||||
le = 0;
|
||||
} else if(!extended_lc && body_size - lc == 1) {
|
||||
le = data[lc];
|
||||
if(le == 0) {
|
||||
le = 256;
|
||||
}
|
||||
} else if(extended_lc && body_size - lc == 2) {
|
||||
le = bit_lib_bytes_to_num_be(&data[lc], sizeof(uint16_t));
|
||||
if(le == 0) {
|
||||
le = 65536;
|
||||
}
|
||||
} else {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer,
|
||||
type_4_tag_bad_params_apdu,
|
||||
sizeof(type_4_tag_bad_params_apdu));
|
||||
error = Type4TagErrorWrongFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
|
||||
error = handler(instance, apdu->p1, apdu->p2, lc, data, le);
|
||||
|
||||
const Iso14443_4aError iso14443_4a_error =
|
||||
iso14443_4a_listener_send_block(instance->iso14443_4a_listener, instance->tx_buffer);
|
||||
|
||||
// Show unknown command on screen
|
||||
if(error == Type4TagErrorCustomCommand) break;
|
||||
|
||||
error = type_4_tag_process_error(iso14443_4a_error);
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "type_4_tag_listener.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Type4TagListenerStateIdle,
|
||||
Type4TagListenerStateSelectedApplication,
|
||||
Type4TagListenerStateSelectedCapabilityContainer,
|
||||
Type4TagListenerStateSelectedNdefMessage,
|
||||
} Type4TagListenerState;
|
||||
|
||||
struct Type4TagListener {
|
||||
Iso14443_4aListener* iso14443_4a_listener;
|
||||
Type4TagData* data;
|
||||
Type4TagListenerState state;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
|
||||
NfcGenericEvent generic_event;
|
||||
Type4TagListenerEvent type_4_tag_event;
|
||||
Type4TagListenerEventData type_4_tag_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
Type4TagError
|
||||
type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#define TAG "Type4TagPoller"
|
||||
|
||||
// Read returns 2 byte status trailer, write sends 5 byte command header
|
||||
#define TYPE_4_TAG_BUF_SIZE (TYPE_4_TAG_ISO_RW_CHUNK_LEN + 5)
|
||||
|
||||
typedef NfcCommand (*Type4TagPollerReadHandler)(Type4TagPoller* instance);
|
||||
|
||||
const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance) {
|
||||
|
||||
@@ -5,19 +5,6 @@
|
||||
|
||||
#define TAG "Type4TagPoller"
|
||||
|
||||
Type4TagError type_4_tag_process_error(Iso14443_4aError error) {
|
||||
switch(error) {
|
||||
case Iso14443_4aErrorNone:
|
||||
return Type4TagErrorNone;
|
||||
case Iso14443_4aErrorNotPresent:
|
||||
return Type4TagErrorNotPresent;
|
||||
case Iso14443_4aErrorTimeout:
|
||||
return Type4TagErrorTimeout;
|
||||
default:
|
||||
return Type4TagErrorProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
Type4TagError type_4_tag_apdu_trx(Type4TagPoller* instance, BitBuffer* tx_buf, BitBuffer* rx_buf) {
|
||||
furi_check(instance);
|
||||
|
||||
@@ -58,7 +45,7 @@ Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Select application");
|
||||
const uint8_t type_4_tag_select_app_apdu[] = {
|
||||
TYPE_4_TAG_ISO_SELECT_CMD,
|
||||
TYPE_4_TAG_ISO_SELECT_P1_BY_NAME,
|
||||
TYPE_4_TAG_ISO_SELECT_P1_BY_DF_NAME,
|
||||
TYPE_4_TAG_ISO_SELECT_P2_EMPTY,
|
||||
TYPE_4_TAG_ISO_APP_NAME_LEN,
|
||||
TYPE_4_TAG_ISO_APP_NAME,
|
||||
|
||||
@@ -33,8 +33,6 @@ struct Type4TagPoller {
|
||||
void* context;
|
||||
};
|
||||
|
||||
Type4TagError type_4_tag_process_error(Iso14443_4aError error);
|
||||
|
||||
const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance);
|
||||
|
||||
Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance);
|
||||
|
||||
Reference in New Issue
Block a user