diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 02b1f09f8..cce1fe94a 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -19,39 +19,48 @@ env.Append( File("protocols/iso14443_3b/iso14443_3b.h"), File("protocols/iso14443_4a/iso14443_4a.h"), File("protocols/iso14443_4b/iso14443_4b.h"), + File("protocols/iso15693_3/iso15693_3.h"), + File("protocols/felica/felica.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/emv/emv.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), - File("protocols/felica/felica.h"), + File("protocols/ntag4xx/ntag4xx.h"), + File("protocols/type_4_tag/type_4_tag.h"), + File("protocols/emv/emv.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), File("protocols/iso14443_4a/iso14443_4a_poller.h"), File("protocols/iso14443_4b/iso14443_4b_poller.h"), + File("protocols/iso15693_3/iso15693_3_poller.h"), + File("protocols/felica/felica_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/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), - File("protocols/felica/felica_poller.h"), + File("protocols/ntag4xx/ntag4xx_poller.h"), + File("protocols/type_4_tag/type_4_tag_poller.h"), + File("protocols/emv/emv_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), + File("protocols/iso15693_3/iso15693_3_listener.h"), + File("protocols/felica/felica_listener.h"), File("protocols/mf_ultralight/mf_ultralight_listener.h"), File("protocols/mf_classic/mf_classic_listener.h"), - File("protocols/felica/felica_listener.h"), + File("protocols/slix/slix_listener.h"), + File("protocols/type_4_tag/type_4_tag_listener.h"), # Sync API File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), + File("protocols/felica/felica_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"), diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index ef9baaabc..1d76f4bf3 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -14,6 +14,8 @@ #define ISO14443_4_BLOCK_PCB_MASK (0x03) #define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_I_MASK (1U << 1) +#define ISO14443_4_BLOCK_PCB_I_ZERO_MASK (7U << 5) #define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2) #define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3) #define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4) @@ -33,8 +35,14 @@ #define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) #define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) +#define ISO14443_4_BLOCK_CID_MASK (0x0F) + #define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask)) +#define ISO14443_4_BLOCK_PCB_IS_I_BLOCK(pcb) \ + (ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_MASK) && \ + (((pcb) & ISO14443_4_BLOCK_PCB_I_ZERO_MASK) == 0)) + #define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) @@ -47,14 +55,23 @@ #define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK) +#define ISO14443_4_LAYER_NAD_NOT_SUPPORTED ((uint8_t) - 1) +#define ISO14443_4_LAYER_NAD_NOT_SET ((uint8_t) - 2) + struct Iso14443_4Layer { uint8_t pcb; uint8_t pcb_prev; + + // Listener specific + uint8_t cid; + uint8_t nad; }; -static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) { +static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance, bool toggle_num) { instance->pcb_prev = instance->pcb; - instance->pcb ^= (uint8_t)0x01; + if(toggle_num) { + instance->pcb ^= (uint8_t)0x01; + } } Iso14443_4Layer* iso14443_4_layer_alloc(void) { @@ -73,6 +90,9 @@ void iso14443_4_layer_reset(Iso14443_4Layer* instance) { furi_assert(instance); instance->pcb_prev = 0; instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; + + instance->cid = ISO14443_4_LAYER_CID_NOT_SUPPORTED; + instance->nad = ISO14443_4_LAYER_NAD_NOT_SUPPORTED; } void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) { @@ -96,7 +116,7 @@ void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool (CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB; } -void iso14443_4_layer_encode_block( +void iso14443_4_layer_encode_command( Iso14443_4Layer* instance, const BitBuffer* input_data, BitBuffer* block_data) { @@ -105,7 +125,7 @@ void iso14443_4_layer_encode_block( bit_buffer_append_byte(block_data, instance->pcb); bit_buffer_append(block_data, input_data); - iso14443_4_layer_update_pcb(instance); + iso14443_4_layer_update_pcb(instance, true); } static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) { @@ -113,7 +133,7 @@ static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_d return data[0]; } -bool iso14443_4_layer_decode_block( +bool iso14443_4_layer_decode_response( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data) { @@ -127,7 +147,7 @@ bool iso14443_4_layer_decode_block( ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); instance->pcb &= ISO14443_4_BLOCK_PCB_MASK; - iso14443_4_layer_update_pcb(instance); + iso14443_4_layer_update_pcb(instance, true); } else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) { const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && @@ -147,7 +167,7 @@ bool iso14443_4_layer_decode_block( return ret; } -Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( +Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data) { @@ -199,3 +219,100 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( return ret; } + +void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid) { + instance->cid = cid; +} + +void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad) { + instance->nad = nad ? 0 : ISO14443_4_LAYER_NAD_NOT_SUPPORTED; +} + +Iso14443_4LayerResult iso14443_4_layer_decode_command( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + uint8_t prologue_len = 0; + instance->pcb = bit_buffer_get_byte(input_data, prologue_len++); + + if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb)) { + if(instance->pcb & ISO14443_4_BLOCK_PCB_I_CID_MASK) { + const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) & + ISO14443_4_BLOCK_CID_MASK; + if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) { + return Iso14443_4LayerResultSkip; + } + } else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) { + return Iso14443_4LayerResultSkip; + } + // TODO: properly handle block chaining + if(instance->pcb & ISO14443_4_BLOCK_PCB_I_NAD_MASK) { + if(instance->nad == ISO14443_4_LAYER_NAD_NOT_SUPPORTED) { + return Iso14443_4LayerResultSkip; + } + instance->nad = bit_buffer_get_byte(input_data, prologue_len++); + } + bit_buffer_copy_right(block_data, input_data, prologue_len); + iso14443_4_layer_update_pcb(instance, false); + return Iso14443_4LayerResultData; + + } else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb)) { + if(instance->pcb & ISO14443_4_BLOCK_PCB_S_CID_MASK) { + const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) & + ISO14443_4_BLOCK_CID_MASK; + if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) { + return Iso14443_4LayerResultSkip; + } + } else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) { + return Iso14443_4LayerResultSkip; + } + if((instance->pcb & ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) == 0) { + // DESELECT + bit_buffer_copy(block_data, input_data); + return Iso14443_4LayerResultSend | Iso14443_4LayerResultHalt; + } else { + // WTX ACK or wrong value + return Iso14443_4LayerResultSkip; + } + + } else if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb)) { + // TODO: properly handle R blocks while chaining + iso14443_4_layer_update_pcb(instance, true); + instance->pcb |= ISO14443_4_BLOCK_PCB_R_NACK_MASK; + bit_buffer_reset(block_data); + bit_buffer_append_byte(block_data, instance->pcb); + iso14443_4_layer_update_pcb(instance, false); + return Iso14443_4LayerResultSend; + } + return Iso14443_4LayerResultSkip; +} + +bool iso14443_4_layer_encode_response( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb_prev)) { + bit_buffer_append_byte(block_data, 0x00); + if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_CID_MASK) { + bit_buffer_append_byte(block_data, instance->cid); + } + // TODO: properly handle block chaining and related R block responses + if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_NAD_MASK && + instance->nad != ISO14443_4_LAYER_NAD_NOT_SET) { + bit_buffer_append_byte(block_data, instance->nad); + instance->nad = ISO14443_4_LAYER_NAD_NOT_SET; + } else { + instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_NAD_MASK; + } + instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_CHAIN_MASK; + bit_buffer_set_byte(block_data, 0, instance->pcb); + bit_buffer_append(block_data, input_data); + iso14443_4_layer_update_pcb(instance, false); + return true; + } + return false; +} diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 9daf25775..1e9ac713f 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -19,21 +19,47 @@ void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present); void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present); -void iso14443_4_layer_encode_block( +// Poller mode + +void iso14443_4_layer_encode_command( Iso14443_4Layer* instance, const BitBuffer* input_data, BitBuffer* block_data); -bool iso14443_4_layer_decode_block( +bool iso14443_4_layer_decode_response( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data); -Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( +Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data); +// Listener mode + +typedef enum { + Iso14443_4LayerResultSkip = (0), + Iso14443_4LayerResultData = (1 << 1), + Iso14443_4LayerResultSend = (1 << 2), + Iso14443_4LayerResultHalt = (1 << 3), +} Iso14443_4LayerResult; + +Iso14443_4LayerResult iso14443_4_layer_decode_command( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +bool iso14443_4_layer_encode_response( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +#define ISO14443_4_LAYER_CID_NOT_SUPPORTED ((uint8_t) - 1) +void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid); + +void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/helpers/nxp_native_command.c b/lib/nfc/helpers/nxp_native_command.c new file mode 100644 index 000000000..d5d535891 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command.c @@ -0,0 +1,115 @@ +#include "nxp_native_command.h" + +#include + +#define TAG "NxpNativeCommand" + +Iso14443_4aError nxp_native_command_iso14443_4a_poller( + Iso14443_4aPoller* iso14443_4a_poller, + NxpNativeCommandStatus* status_code, + const BitBuffer* input_buffer, + BitBuffer* result_buffer, + NxpNativeCommandMode command_mode, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_check(iso14443_4a_poller); + furi_check(tx_buffer); + furi_check(rx_buffer); + furi_check(input_buffer); + furi_check(result_buffer); + furi_check(command_mode < NxpNativeCommandModeMAX); + + Iso14443_4aError error = Iso14443_4aErrorNone; + *status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + + do { + bit_buffer_reset(tx_buffer); + if(command_mode == NxpNativeCommandModePlain) { + bit_buffer_append(tx_buffer, input_buffer); + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA); + bit_buffer_append_byte(tx_buffer, bit_buffer_get_byte(input_buffer, 0)); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2); + if(bit_buffer_get_size_bytes(input_buffer) > 1) { + bit_buffer_append_byte(tx_buffer, bit_buffer_get_size_bytes(input_buffer) - 1); + bit_buffer_append_right(tx_buffer, input_buffer, 1); + } + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE); + } + + bit_buffer_reset(rx_buffer); + error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer); + + if(error != Iso14443_4aErrorNone) { + break; + } + + bit_buffer_reset(tx_buffer); + if(command_mode == NxpNativeCommandModePlain) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME); + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buffer); + *status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + bit_buffer_reset(result_buffer); + if(command_mode == NxpNativeCommandModePlain && response_len >= sizeof(uint8_t)) { + *status_code = bit_buffer_get_byte(rx_buffer, 0); + if(response_len > sizeof(uint8_t)) { + bit_buffer_copy_right(result_buffer, rx_buffer, sizeof(uint8_t)); + } + } else if( + command_mode == NxpNativeCommandModeIsoWrapped && + response_len >= 2 * sizeof(uint8_t) && + bit_buffer_get_byte(rx_buffer, response_len - 2) == NXP_NATIVE_COMMAND_ISO_SW1) { + *status_code = bit_buffer_get_byte(rx_buffer, response_len - 1); + if(response_len > 2 * sizeof(uint8_t)) { + bit_buffer_copy_left(result_buffer, rx_buffer, response_len - 2 * sizeof(uint8_t)); + } + } + + while(*status_code == NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME) { + bit_buffer_reset(rx_buffer); + error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer); + + if(error != Iso14443_4aErrorNone) { + break; + } + + const size_t rx_size = bit_buffer_get_size_bytes(rx_buffer); + const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(result_buffer) - + bit_buffer_get_size_bytes(result_buffer); + + if(command_mode == NxpNativeCommandModePlain) { + *status_code = rx_size >= 1 ? bit_buffer_get_byte(rx_buffer, 0) : + NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + if(rx_size <= rx_capacity_remaining + 1) { + bit_buffer_append_right(result_buffer, rx_buffer, sizeof(uint8_t)); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); + } + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + if(rx_size >= 2 && + bit_buffer_get_byte(rx_buffer, rx_size - 2) == NXP_NATIVE_COMMAND_ISO_SW1) { + *status_code = bit_buffer_get_byte(rx_buffer, rx_size - 1); + } else { + *status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + } + if(rx_size <= rx_capacity_remaining + 2) { + bit_buffer_set_size_bytes(rx_buffer, rx_size - 2); + bit_buffer_append(result_buffer, rx_buffer); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 2); + } + } + } + } while(false); + + return error; +} diff --git a/lib/nfc/helpers/nxp_native_command.h b/lib/nfc/helpers/nxp_native_command.h new file mode 100644 index 000000000..61677a3f6 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command.h @@ -0,0 +1,92 @@ +#pragma once + +#include "nxp_native_command_mode.h" + +#include + +// ISO 7816 command wrapping +#define NXP_NATIVE_COMMAND_ISO_CLA (0x90) +#define NXP_NATIVE_COMMAND_ISO_P1 (0x00) +#define NXP_NATIVE_COMMAND_ISO_P2 (0x00) +#define NXP_NATIVE_COMMAND_ISO_LE (0x00) +// ISO 7816 status wrapping +#define NXP_NATIVE_COMMAND_ISO_SW1 (0x91) + +// Successful operation +#define NXP_NATIVE_COMMAND_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define NXP_NATIVE_COMMAND_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define NXP_NATIVE_COMMAND_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define NXP_NATIVE_COMMAND_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define NXP_NATIVE_COMMAND_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define NXP_NATIVE_COMMAND_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define NXP_NATIVE_COMMAND_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define NXP_NATIVE_COMMAND_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define NXP_NATIVE_COMMAND_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Currently not allowed to authenticate. Keep trying until full delay is spent +#define NXP_NATIVE_COMMAND_STATUS_STATUS_AUTHENTICATION_DELAY (0xAD) +// Current authentication status does not allow the requested command +#define NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME (0xAF) +// Attempt to read/write data from/to beyond the file's/record's limits +// Attempt to exceed the limits of a value file. +#define NXP_NATIVE_COMMAND_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define NXP_NATIVE_COMMAND_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define NXP_NATIVE_COMMAND_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define NXP_NATIVE_COMMAND_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define NXP_NATIVE_COMMAND_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define NXP_NATIVE_COMMAND_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define NXP_NATIVE_COMMAND_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define NXP_NATIVE_COMMAND_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define NXP_NATIVE_COMMAND_STATUS_FILE_INTEGRITY_ERROR (0xF1) + +typedef uint8_t NxpNativeCommandStatus; + +/** + * @brief Transmit and receive NXP Native Command chunks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The result_buffer will be filled with any data received as a response to data + * sent from input_buffer, with a timeout defined by the fwt parameter. + * + * The tx_buffer and rx_buffer are used as working areas to handle individual + * command chunks and responses. + * + * @param[in, out] iso14443_4a_poller pointer to the instance to be used in the transaction. + * @param[out] status_code pointer to a status variable to hold the result of the operation. + * @param[in] input_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] result_buffer pointer to the buffer to be filled with received data. + * @param[in] command_mode what command formatting mode to use for the transaction. + * @param[in, out] tx_buffer pointer to the buffer for command construction. + * @param[in, out] rx_buffer pointer to the buffer for response handling. + * @return Iso14443_4aErrorNone and STATUS_OPERATION_OK on success, an error code on failure. + */ +Iso14443_4aError nxp_native_command_iso14443_4a_poller( + Iso14443_4aPoller* iso14443_4a_poller, + NxpNativeCommandStatus* status_code, + const BitBuffer* input_buffer, + BitBuffer* result_buffer, + NxpNativeCommandMode command_mode, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer); diff --git a/lib/nfc/helpers/nxp_native_command_mode.h b/lib/nfc/helpers/nxp_native_command_mode.h new file mode 100644 index 000000000..7684b4d48 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command_mode.h @@ -0,0 +1,11 @@ +#pragma once + +/** + * @brief Enumeration of possible command modes. + */ +typedef enum { + NxpNativeCommandModePlain, /**< Plain native commands. */ + NxpNativeCommandModeIsoWrapped, /**< ISO 7816-wrapped commands. */ + + NxpNativeCommandModeMAX, +} NxpNativeCommandMode; diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 4f4358711..90e65b282 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -646,33 +646,6 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } -NfcError nfc_iso15693_detect_mode(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_detect_mode(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - -NfcError nfc_iso15693_force_1outof4(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof4(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - -NfcError nfc_iso15693_force_1outof256(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof256(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - NfcError nfc_felica_listener_set_sensf_res_data( Nfc* instance, const uint8_t* idm, diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index ebd29dc4b..8fbf90d1f 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -380,30 +380,6 @@ NfcError nfc_felica_listener_set_sensf_res_data( */ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); -/** - * @brief Set ISO15693 parser mode to autodetect - * -* @param[in,out] instance pointer to the instance to be configured. - * @returns NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_detect_mode(Nfc* instance); - -/** - * @brief Set ISO15693 parser mode to 1OutOf4, disables autodetection - * - * @param[in,out] instance pointer to the instance to be configured. - * @return NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_force_1outof4(Nfc* instance); - -/** - * @brief Set ISO15693 parser mode to 1OutOf256, disables autodetection - * - * @param[in,out] instance pointer to the instance to be configured. - * @return NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_force_1outof256(Nfc* instance); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h index 8a550ca0a..7b2a4424d 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h @@ -26,6 +26,47 @@ typedef struct { Iso14443_3aListenerEventData* data; } Iso14443_3aListenerEvent; +/** + * @brief Transmit Iso14443_3a frames in listener mode. + * + * Must ONLY be used inside the callback function. + * + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); + +/** + * @brief Transmit Iso14443_3a frames with custom parity bits in listener mode. + * + * Must ONLY be used inside the callback function. + * + * Custom parity bits must be set in the tx_buffer. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +/** + * @brief Transmit Iso14443_3a standard frames in listener mode. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h index 0113a1cb8..ef9edf18f 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h @@ -26,17 +26,6 @@ struct Iso14443_3aListener { void* context; }; -Iso14443_3aError - iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); - -Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( - Iso14443_3aListener* instance, - const BitBuffer* tx_buffer); - -Iso14443_3aError iso14443_3a_listener_send_standard_frame( - Iso14443_3aListener* instance, - const BitBuffer* tx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h index 9b0230975..f28b26ddd 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -2,7 +2,8 @@ #include "iso14443_4a.h" -#define ISO14443_4A_CMD_READ_ATS (0xE0) +#define ISO14443_4A_CMD_READ_ATS (0xE0) +#define ISO14443_4A_READ_ATS_CID_MASK (0x0F) // ATS bit definitions #define ISO14443_4A_ATS_T0_TA1 (1U << 4) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 2519fb90c..95bae45d4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -14,7 +14,9 @@ static Iso14443_4aListener* Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener)); instance->iso14443_3a_listener = iso14443_3a_listener; instance->data = data; + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; @@ -27,13 +29,18 @@ static Iso14443_4aListener* static void iso14443_4a_listener_free(Iso14443_4aListener* instance) { furi_assert(instance); - furi_assert(instance->data); - furi_assert(instance->tx_buffer); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->rx_buffer); bit_buffer_free(instance->tx_buffer); free(instance); } +static void iso14443_4a_listener_reset(Iso14443_4aListener* instance) { + instance->state = Iso14443_4aListenerStateIdle; + iso14443_4_layer_reset(instance->iso14443_4_layer); +} + static void iso14443_4a_listener_set_callback( Iso14443_4aListener* instance, NfcGenericCallback callback, @@ -68,20 +75,46 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) == Iso14443_4aErrorNone) { instance->state = Iso14443_4aListenerStateActive; + if(iso14443_4a_supports_frame_option( + instance->data, Iso14443_4aFrameOptionCid)) { + const uint8_t cid = bit_buffer_get_byte(rx_buffer, 1) & + ISO14443_4A_READ_ATS_CID_MASK; + iso14443_4_layer_set_cid(instance->iso14443_4_layer, cid); + } + iso14443_4_layer_set_nad_supported( + instance->iso14443_4_layer, + iso14443_4a_supports_frame_option( + instance->data, Iso14443_4aFrameOptionNad)); } } } else { - instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; - instance->iso14443_4a_event.data->buffer = rx_buffer; + Iso14443_4LayerResult status = iso14443_4_layer_decode_command( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); + if(status & Iso14443_4LayerResultSend) { + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->rx_buffer); + } + if(status & Iso14443_4LayerResultHalt) { + iso14443_4a_listener_reset(instance); + if(instance->callback) { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeHalted; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } + if(status & Iso14443_4LayerResultData) { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; + instance->iso14443_4a_event.data->buffer = instance->rx_buffer; - if(instance->callback) { - command = instance->callback(instance->generic_event, instance->context); + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } } } } else if( iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { - instance->state = Iso14443_4aListenerStateIdle; + iso14443_4a_listener_reset(instance); instance->iso14443_4a_event.type = iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ? diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h index 04f0b197a..ff4bad7e4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -25,6 +25,18 @@ typedef struct { Iso14443_4aListenerEventData* data; } Iso14443_4aListenerEvent; +/** + * @brief Transmit Iso14443_4a blocks in listener mode. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError + iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c index 8590c22ad..6ea5ae5e4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c @@ -30,3 +30,17 @@ Iso14443_4aError instance->iso14443_3a_listener, instance->tx_buffer); return iso14443_4a_process_error(error); } + +Iso14443_4aError + iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer) { + bit_buffer_reset(instance->tx_buffer); + + if(!iso14443_4_layer_encode_response( + instance->iso14443_4_layer, tx_buffer, instance->tx_buffer)) { + return Iso14443_4aErrorProtocol; + } + + const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + return iso14443_4a_process_error(error); +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h index d4e884f6f..c0ed6d1cb 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "iso14443_4a_listener.h" #include "iso14443_4a_i.h" @@ -17,8 +18,10 @@ typedef enum { struct Iso14443_4aListener { Iso14443_3aListener* iso14443_3a_listener; Iso14443_4aData* data; + Iso14443_4Layer* iso14443_4_layer; Iso14443_4aListenerState state; + BitBuffer* rx_buffer; BitBuffer* tx_buffer; NfcGenericEvent generic_event; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a2342a92d..97c512205 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -65,7 +65,7 @@ Iso14443_4aError iso14443_4a_poller_send_block( furi_check(rx_buffer); bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4aError error = Iso14443_4aErrorNone; @@ -106,7 +106,7 @@ Iso14443_4aError iso14443_4a_poller_send_block( } while(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX)); } - if(!iso14443_4_layer_decode_block( + if(!iso14443_4_layer_decode_response( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4aErrorProtocol; break; @@ -155,7 +155,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS; bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4aError error = Iso14443_4aErrorNone; @@ -180,7 +180,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( break; } else { - error = iso14443_4_layer_decode_block_pwt_ext( + error = iso14443_4_layer_decode_response_pwt_ext( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); if(error == Iso14443_4aErrorSendExtra) { if(--attempts_left == 0) break; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c index da82e1417..2db2da9c1 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c @@ -24,7 +24,7 @@ Iso14443_4bError iso14443_4b_poller_send_block( furi_check(rx_buffer); bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4bError error = Iso14443_4bErrorNone; @@ -36,7 +36,7 @@ Iso14443_4bError iso14443_4b_poller_send_block( error = iso14443_4b_process_error(iso14443_3b_error); break; - } else if(!iso14443_4_layer_decode_block( + } else if(!iso14443_4_layer_decode_response( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4bErrorProtocol; break; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index ec60b336b..cb5c6cac8 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -17,6 +17,13 @@ extern "C" { #define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F) #define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5) +#define MF_DESFIRE_CMD_CREATE_APPLICATION (0xCA) +#define MF_DESFIRE_CMD_CREATE_STD_DATA_FILE (0xCD) +#define MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE (0xCB) +#define MF_DESFIRE_CMD_CREATE_VALUE_FILE (0xCC) +#define MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE (0xC1) +#define MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE (0xC0) + #define MF_DESFIRE_CMD_READ_DATA (0xBD) #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index eba9c4312..b90ef8ccf 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -60,7 +60,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion)); } - return can_parse; + return can_parse && (data->hw_type & 0x0F) == 0x01; } bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { @@ -81,17 +81,17 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu return can_parse; } -bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { - typedef struct FURI_PACKED { - bool is_master_key_changeable : 1; - bool is_free_directory_list : 1; - bool is_free_create_delete : 1; - bool is_config_changeable : 1; - uint8_t change_key_id : 4; - uint8_t max_keys : 4; - uint8_t flags : 4; - } MfDesfireKeySettingsLayout; +typedef struct FURI_PACKED { + bool is_master_key_changeable : 1; + bool is_free_directory_list : 1; + bool is_free_create_delete : 1; + bool is_config_changeable : 1; + uint8_t change_key_id : 4; + uint8_t max_keys : 4; + uint8_t flags : 4; +} MfDesfireKeySettingsLayout; +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout); if(can_parse) { @@ -111,6 +111,21 @@ bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* return can_parse; } +void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf) { + MfDesfireKeySettingsLayout layout; + + layout.is_master_key_changeable = data->is_master_key_changeable; + layout.is_free_directory_list = data->is_free_directory_list; + layout.is_free_create_delete = data->is_free_create_delete; + layout.is_config_changeable = data->is_config_changeable; + + layout.change_key_id = data->change_key_id; + layout.max_keys = data->max_keys; + layout.flags = data->flags; + + bit_buffer_append_bytes(buf, (uint8_t*)&layout, sizeof(MfDesfireKeySettingsLayout)); +} + bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 921bbb9de..ac572ae58 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -2,55 +2,11 @@ #include "mf_desfire.h" +#include + #define MF_DESFIRE_FFF_PICC_PREFIX "PICC" #define MF_DESFIRE_FFF_APP_PREFIX "Application" -// Successful operation -#define MF_DESFIRE_STATUS_OPERATION_OK (0x00) -// No changes done to backup files, CommitTransaction / AbortTransaction not necessary -#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C) -// Insufficient NV-Memory to complete command -#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E) -// Command code not supported -#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C) -// CRC or MAC does not match data Padding bytes not valid -#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E) -// Invalid key number specified -#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40) -// Length of command string invalid -#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E) -// Current configuration / status does not allow the requested command -#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D) -// Value of the parameter(s) invalid -#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E) -// Requested AID not present on PICC -#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0) -// Unrecoverable error within application, application will be disabled -#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1) -// Current authentication status does not allow the requested command -#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE) -// Additional data frame is expected to be sent -#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF) -// Attempt to read/write data from/to beyond the file's/record's limits -// Attempt to exceed the limits of a value file. -#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE) -// Unrecoverable error within PICC, PICC will be disabled -#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1) -// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD -#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA) -// PICC was disabled by an unrecoverable error -#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD) -// Number of Applications limited to 28, no additional CreateApplication possible -#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE) -// Creation of file/application failed because file/application with same number already exists -#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE) -// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated -#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE) -// Specified file number does not exist -#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0) -// Unrecoverable error within file, file will be disabled -#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1) - // SimpleArray configurations extern const SimpleArrayConfig mf_desfire_key_version_array_config; @@ -68,6 +24,8 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf); +void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf); + bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf); bool mf_desfire_application_id_parse( diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 45e5a27f9..e9c5e3b6d 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -251,8 +251,7 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { MfDesfireError error = mf_desfire_poller_read_key_version(instance, 0, &key_version); if(error != MfDesfireErrorNone) break; - MfDesfireVersion version = {}; - error = mf_desfire_poller_read_version(instance, &version); + error = mf_desfire_poller_read_version(instance, &instance->data->version); if(error != MfDesfireErrorNone) break; protocol_detected = true; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h index 707df42cd..4010938e5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -3,6 +3,7 @@ #include "mf_desfire.h" #include +#include #ifdef __cplusplus extern "C" { @@ -38,6 +39,16 @@ typedef struct { MfDesfirePollerEventData* data; /**< Pointer to event specific data. */ } MfDesfirePollerEvent; +/** + * @brief Change command mode used in poller mode. + * + * @param[in, out] instance pointer to the instance to affect. + * @param[in] command_mode command mode to use in further communication with the card. + */ +void mf_desfire_poller_set_command_mode( + MfDesfirePoller* instance, + NxpNativeCommandMode command_mode); + /** * @brief Transmit and receive MfDesfire chunks in poller mode. * @@ -51,11 +62,16 @@ typedef struct { * @param[out] rx_buffer pointer to the buffer to be filled with received data. * @return MfDesfireErrorNone on success, an error code on failure. */ -MfDesfireError mf_desfire_send_chunks( +MfDesfireError mf_desfire_poller_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +/** + * @warning deprecated, use mf_desfire_poller_send_chunks instead + */ +#define mf_desfire_send_chunks mf_desfire_poller_send_chunks + /** * @brief Read MfDesfire card version. * @@ -187,6 +203,44 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( const SimpleArray* file_ids, SimpleArray* data); +/** + * @brief Create Application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id pointer to the application id for the new application. + * @param[in] key_settings pointer to the key settings for the new application. + * @param[in] iso_df_id optional iso identifier for the new application. + * @param[in] iso_df_name optional iso name for the new application. + * @param[in] iso_df_name_len length of the optional iso application name. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_create_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id, + const MfDesfireKeySettings* key_settings, + uint16_t iso_df_id, + const uint8_t* iso_df_name, + uint8_t iso_df_name_len); + +/** + * @brief Create File on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id for the new file. + * @param[in] data pointer to the file settings for the new file. + * @param[in] iso_ef_id optional iso identifier for the new file. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_create_file( + MfDesfirePoller* instance, + MfDesfireFileId id, + const MfDesfireFileSettings* data, + uint16_t iso_ef_id); + /** * @brief Read file data on MfDesfire card. * diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 8b57fcc4c..a44395c60 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -1,6 +1,7 @@ #include "mf_desfire_poller_i.h" #include +#include #include "mf_desfire_i.h" @@ -21,76 +22,48 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { switch(status_code) { - case MF_DESFIRE_STATUS_OPERATION_OK: + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: return MfDesfireErrorNone; - case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + case NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR: return MfDesfireErrorAuthentication; - case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE: + case NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE: return MfDesfireErrorCommandNotSupported; default: return MfDesfireErrorProtocol; } } -MfDesfireError mf_desfire_send_chunks( +void mf_desfire_poller_set_command_mode( + MfDesfirePoller* instance, + NxpNativeCommandMode command_mode) { + furi_check(instance); + furi_check(instance->state == MfDesfirePollerStateIdle); + furi_check(command_mode < NxpNativeCommandModeMAX); + + instance->command_mode = command_mode; +} + +MfDesfireError mf_desfire_poller_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { furi_check(instance); - furi_check(instance->iso14443_4a_poller); - furi_check(instance->tx_buffer); - furi_check(instance->rx_buffer); - furi_check(tx_buffer); - furi_check(rx_buffer); - MfDesfireError error = MfDesfireErrorNone; + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + instance->command_mode, + instance->tx_buffer, + instance->rx_buffer); - do { - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_desfire_process_error(iso14443_4a_error); - break; - } - - bit_buffer_reset(instance->tx_buffer); - bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME); - - if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { - bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - } else { - bit_buffer_reset(rx_buffer); - } - - while( - bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) { - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_desfire_process_error(iso14443_4a_error); - break; - } - - const size_t rx_size = bit_buffer_get_size_bytes(instance->rx_buffer); - const size_t rx_capacity_remaining = - bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - - if(rx_size <= rx_capacity_remaining + 1) { - bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - } else { - FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); - } - } - } while(false); - - if(error == MfDesfireErrorNone) { - uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0); - error = mf_desfire_process_status_code(err_code); + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return mf_desfire_process_error(iso14443_4a_error); } - return error; + return mf_desfire_process_status_code(status_code); } MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { @@ -102,7 +75,8 @@ MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfi MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -124,7 +98,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -146,7 +121,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -170,7 +146,7 @@ MfDesfireError mf_desfire_poller_read_key_version( bit_buffer_set_byte(instance->input_buffer, 1, key_num); MfDesfireError error = - mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error == MfDesfireErrorNone) { if(!mf_desfire_key_version_parse(data, instance->result_buffer)) { error = MfDesfireErrorProtocol; @@ -210,7 +186,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -243,7 +220,7 @@ MfDesfireError mf_desfire_poller_select_application( instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); MfDesfireError error = - mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); return error; } @@ -258,7 +235,8 @@ MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, Simple MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -293,7 +271,8 @@ MfDesfireError mf_desfire_poller_read_file_settings( MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -329,6 +308,108 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( return error; } +MfDesfireError mf_desfire_poller_create_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id, + const MfDesfireKeySettings* key_settings, + uint16_t iso_df_id, + const uint8_t* iso_df_name, + uint8_t iso_df_name_len) { + furi_check(instance); + furi_check(key_settings); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_CREATE_APPLICATION); + bit_buffer_append_bytes( + instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); + mf_desfire_key_settings_dump(key_settings, instance->input_buffer); + + if(iso_df_name && iso_df_name_len) { + uint8_t ks2_pos = bit_buffer_get_size_bytes(instance->input_buffer) - 1; + uint8_t ks2 = bit_buffer_get_byte(instance->input_buffer, ks2_pos); + ks2 |= (1 << 5); // Mark file id present + bit_buffer_set_byte(instance->input_buffer, ks2_pos, ks2); + + uint8_t iso_df_id_le[sizeof(iso_df_id)]; + bit_lib_num_to_bytes_le(iso_df_id, sizeof(iso_df_id_le), iso_df_id_le); + bit_buffer_append_bytes(instance->input_buffer, iso_df_id_le, sizeof(iso_df_id_le)); + + bit_buffer_append_bytes(instance->input_buffer, iso_df_name, iso_df_name_len); + } + + MfDesfireError error = + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + +MfDesfireError mf_desfire_poller_create_file( + MfDesfirePoller* instance, + MfDesfireFileId id, + const MfDesfireFileSettings* data, + uint16_t iso_ef_id) { + furi_check(instance); + furi_check(data); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte( + instance->input_buffer, + data->type == MfDesfireFileTypeStandard ? MF_DESFIRE_CMD_CREATE_STD_DATA_FILE : + data->type == MfDesfireFileTypeBackup ? MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE : + data->type == MfDesfireFileTypeValue ? MF_DESFIRE_CMD_CREATE_VALUE_FILE : + data->type == MfDesfireFileTypeLinearRecord ? MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE : + data->type == MfDesfireFileTypeCyclicRecord ? MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE : + 0x00); + bit_buffer_append_byte(instance->input_buffer, id); + if(iso_ef_id) { + uint8_t iso_ef_id_le[sizeof(iso_ef_id)]; + bit_lib_num_to_bytes_le(iso_ef_id, sizeof(iso_ef_id_le), iso_ef_id_le); + bit_buffer_append_bytes(instance->input_buffer, iso_ef_id_le, sizeof(iso_ef_id_le)); + } + bit_buffer_append_byte(instance->input_buffer, data->comm); + bit_buffer_append_bytes( + instance->input_buffer, + (const uint8_t*)data->access_rights, + sizeof(MfDesfireFileAccessRights) * data->access_rights_len); + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + uint8_t data_size_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->data.size, sizeof(data_size_le), data_size_le); + bit_buffer_append_bytes(instance->input_buffer, data_size_le, sizeof(data_size_le)); + + } else if(data->type == MfDesfireFileTypeValue) { + uint8_t lo_limit_le[sizeof(data->value.lo_limit)]; + bit_lib_num_to_bytes_le(data->value.lo_limit, sizeof(lo_limit_le), lo_limit_le); + bit_buffer_append_bytes(instance->input_buffer, lo_limit_le, sizeof(lo_limit_le)); + + uint8_t hi_limit_le[sizeof(data->value.hi_limit)]; + bit_lib_num_to_bytes_le(data->value.hi_limit, sizeof(hi_limit_le), hi_limit_le); + bit_buffer_append_bytes(instance->input_buffer, hi_limit_le, sizeof(hi_limit_le)); + + uint8_t value_le[sizeof(data->value.limited_credit_value)]; + bit_lib_num_to_bytes_le(data->value.limited_credit_value, sizeof(value_le), value_le); + bit_buffer_append_bytes(instance->input_buffer, value_le, sizeof(value_le)); + + bit_buffer_append_byte(instance->input_buffer, data->value.limited_credit_enabled); + + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + uint8_t record_size_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->record.size, sizeof(record_size_le), record_size_le); + bit_buffer_append_bytes(instance->input_buffer, record_size_le, sizeof(record_size_le)); + + uint8_t record_max_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->record.max, sizeof(record_max_le), record_max_le); + bit_buffer_append_bytes(instance->input_buffer, record_max_le, sizeof(record_max_le)); + } + + MfDesfireError error = + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + static MfDesfireError mf_desfire_poller_read_file( MfDesfirePoller* instance, MfDesfireFileId id, @@ -354,7 +435,8 @@ static MfDesfireError mf_desfire_poller_read_file( bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3); - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer); @@ -400,7 +482,8 @@ MfDesfireError mf_desfire_poller_read_file_value( MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index 19e38bebb..179fd44b5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -30,6 +30,7 @@ typedef enum { struct MfDesfirePoller { Iso14443_4aPoller* iso14443_4a_poller; + NxpNativeCommandMode command_mode; MfDesfirePollerSessionState session_state; MfDesfirePollerState state; MfDesfireError error; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index bd32956d6..b66cf5ea2 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -15,8 +15,8 @@ const uint8_t mf_plus_ats_t1_tk_values[][MF_PLUS_T1_TK_VALUE_LEN] = { {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 + {0xC1, 0x05, 0x21, 0x30, 0x00, 0xF6, 0xD1}, // Mifare Plus SE + {0xC1, 0x05, 0x21, 0x30, 0x10, 0xF6, 0xD1}, // Mifare Plus SE }; MfPlusError mf_plus_get_type_from_version( @@ -27,7 +27,7 @@ MfPlusError mf_plus_get_type_from_version( MfPlusError error = MfPlusErrorProtocol; - if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) { + if((mf_plus_data->version.hw_type & 0x0F) == 0x02) { error = MfPlusErrorNone; // Mifare Plus EV1/EV2 @@ -85,16 +85,15 @@ MfPlusError MfPlusError error = MfPlusErrorProtocol; - if(simple_array_get_count(iso4_data->ats_data.t1_tk) != MF_PLUS_T1_TK_VALUE_LEN) { + const size_t historical_bytes_len = simple_array_get_count(iso4_data->ats_data.t1_tk); + if(historical_bytes_len != MF_PLUS_T1_TK_VALUE_LEN) { return MfPlusErrorProtocol; } + const uint8_t* historical_bytes = simple_array_cget_data(iso4_data->ats_data.t1_tk); 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) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 2K SL1 mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize2K; @@ -102,11 +101,7 @@ MfPlusError 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) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { // Mifare Plus X 2K SL1 mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize2K; @@ -115,14 +110,8 @@ MfPlusError 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) { + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 || + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) { // Mifare Plus SE 1K SL1 mf_plus_data->type = MfPlusTypeSE; mf_plus_data->size = MfPlusSize1K; @@ -154,10 +143,7 @@ MfPlusError 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) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 4K SL1 mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize4K; @@ -165,11 +151,7 @@ MfPlusError 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) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { // Mifare Plus X 4K SL1 mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize4K; @@ -183,10 +165,7 @@ MfPlusError 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) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 2/4K SL3 FURI_LOG_D(TAG, "Mifare Plus S SL3"); mf_plus_data->type = MfPlusTypeS; @@ -207,21 +186,20 @@ MfPlusError } 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) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { + // Mifare Plus X 2/4K SL3 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) { + // Mifare Plus X 2K SL3 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) { + // Mifare Plus X 4K SL3 mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); @@ -229,6 +207,16 @@ MfPlusError } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); } + } else if( + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 || + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) { + // Mifare Plus SE 1K SL3 + mf_plus_data->type = MfPlusTypeSE; + mf_plus_data->size = MfPlusSize1K; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL3"); + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); } @@ -238,22 +226,12 @@ MfPlusError } MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { - bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); if(can_parse) { bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); - } else if( - bit_buffer_get_size_bytes(buf) == 8 && - bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) { - // HACK(-nofl): There are supposed to be three parts to the GetVersion command, - // with the second and third parts fetched by sending the AdditionalFrame - // command. I don't know whether the entire MIFARE Plus line uses status as - // the first byte, so let's just assume we only have the first part of - // the response if it's size 8 and starts with the AF status. The second - // part of the response is the same size and status byte, but so far - // we're only reading one response. - can_parse = true; - bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1); + } else { + memset(data, 0, sizeof(MfPlusVersion)); } return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index 302f5a178..cadc435b9 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -2,10 +2,9 @@ #include "mf_plus.h" -#define MF_PLUS_FFF_PICC_PREFIX "PICC" +#include -#define MF_PLUS_STATUS_OPERATION_OK (0x90) -#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF) +#define MF_PLUS_FFF_PICC_PREFIX "PICC" MfPlusError mf_plus_get_type_from_version( const Iso14443_4aData* iso14443_4a_data, diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index cab906f1d..b2e4231ff 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -19,28 +19,36 @@ MfPlusError mf_plus_process_error(Iso14443_4aError error) { } } -MfPlusError mf_plus_poller_send_chunk( +MfPlusError mf_plus_process_status_code(uint8_t status_code) { + switch(status_code) { + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: + return MfPlusErrorNone; + default: + return MfPlusErrorProtocol; + } +} + +MfPlusError mf_plus_poller_send_chunks( 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); + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + NxpNativeCommandModePlain, + instance->tx_buffer, + instance->rx_buffer); - if(error == MfPlusErrorNone) { - bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return mf_plus_process_error(iso14443_4a_error); } - bit_buffer_reset(instance->tx_buffer); - - return error; + return mf_plus_process_status_code(status_code); } MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) { @@ -50,7 +58,7 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d 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); + mf_plus_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error == MfPlusErrorNone) { error = mf_plus_version_parse(data, instance->result_buffer); } diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 2b9714613..0f1766029 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -22,9 +22,11 @@ #include #include #include -#include #include #include +#include +#include +#include /** * @brief List of registered NFC device implementations. @@ -45,6 +47,8 @@ const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, + [NfcProtocolNtag4xx] = &nfc_device_ntag4xx, + [NfcProtocolType4Tag] = &nfc_device_type_4_tag, [NfcProtocolEmv] = &nfc_device_emv, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 5ad73f6fe..7efb274b6 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -3,10 +3,11 @@ #include #include #include +#include #include #include #include -#include +#include const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, @@ -14,11 +15,14 @@ const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, [NfcProtocolIso14443_4b] = NULL, [NfcProtocolIso15693_3] = &nfc_listener_iso15693_3, + [NfcProtocolFelica] = &nfc_listener_felica, [NfcProtocolMfUltralight] = &mf_ultralight_listener, [NfcProtocolMfClassic] = &mf_classic_listener, + [NfcProtocolMfPlus] = NULL, [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, - [NfcProtocolFelica] = &nfc_listener_felica, + [NfcProtocolNtag4xx] = NULL, + [NfcProtocolType4Tag] = &nfc_listener_type_4_tag, [NfcProtocolEmv] = NULL, }; diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index 21eef26ba..afcd9ab36 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -10,9 +10,11 @@ #include #include #include -#include #include #include +#include +#include +#include const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, @@ -27,6 +29,8 @@ const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, [NfcProtocolSt25tb] = &nfc_poller_st25tb, + [NfcProtocolNtag4xx] = &ntag4xx_poller, + [NfcProtocolType4Tag] = &type_4_tag_poller, [NfcProtocolEmv] = &emv_poller, /* Add new pollers here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 4106e689a..6767deb07 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -12,19 +12,19 @@ * ``` * **************************** Protocol tree structure *************************** * - * (Start) - * | - * +------------------------+-----------+---------+------------+ - * | | | | | - * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB - * | | | - * +---------------+-------------+ ISO14443-4B SLIX - * | | | - * ISO14443-4A Mf Ultralight Mf Classic - * | - * +-----+-----+ - * | | - * Mf Desfire EMV + * (Start) + * | + * +------------------------+-----------+---------+------------+ + * | | | | | + * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB + * | | | + * +---------------+-------------+ ISO14443-4B SLIX + * | | | + * ISO14443-4A Mf Ultralight Mf Classic + * | + * +-----+----+----------+----------+---------+ + * | | | | | + * Mf Desfire Mf Plus NTAG4xx Type 4 Tag EMV * ``` * * When implementing a new protocol, its place in the tree must be determined first. @@ -62,8 +62,10 @@ 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, + NfcProtocolMfDesfire, + NfcProtocolNtag4xx, + NfcProtocolType4Tag, NfcProtocolEmv, }; @@ -156,6 +158,18 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolNtag4xx] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolType4Tag] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, [NfcProtocolEmv] = { .parent_protocol = NfcProtocolIso14443_4a, diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 12866528e..d33624d15 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -188,6 +188,8 @@ typedef enum { NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, + NfcProtocolNtag4xx, + NfcProtocolType4Tag, NfcProtocolEmv, /* Add new protocols here */ diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx.c b/lib/nfc/protocols/ntag4xx/ntag4xx.c new file mode 100644 index 000000000..7b0991903 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx.c @@ -0,0 +1,192 @@ +#include "ntag4xx_i.h" + +#include + +#define NTAG4XX_PROTOCOL_NAME "NTAG4xx" + +#define NTAG4XX_HW_MAJOR_TYPE_413_DNA (0x10) +#define NTAG4XX_HW_MAJOR_TYPE_424_DNA (0x30) + +#define NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG (0x08) + +static const char* ntag4xx_type_strings[] = { + [Ntag4xxType413DNA] = "NTAG413 DNA", + [Ntag4xxType424DNA] = "NTAG424 DNA", + [Ntag4xxType424DNATT] = "NTAG424 DNA TagTamper", + [Ntag4xxType426QDNA] = "NTAG426Q DNA", + [Ntag4xxType426QDNATT] = "NTAG426Q DNA TagTamper", + [Ntag4xxTypeUnknown] = "UNK", +}; + +const NfcDeviceBase nfc_device_ntag4xx = { + .protocol_name = NTAG4XX_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)ntag4xx_alloc, + .free = (NfcDeviceFree)ntag4xx_free, + .reset = (NfcDeviceReset)ntag4xx_reset, + .copy = (NfcDeviceCopy)ntag4xx_copy, + .verify = (NfcDeviceVerify)ntag4xx_verify, + .load = (NfcDeviceLoad)ntag4xx_load, + .save = (NfcDeviceSave)ntag4xx_save, + .is_equal = (NfcDeviceEqual)ntag4xx_is_equal, + .get_name = (NfcDeviceGetName)ntag4xx_get_device_name, + .get_uid = (NfcDeviceGetUid)ntag4xx_get_uid, + .set_uid = (NfcDeviceSetUid)ntag4xx_set_uid, + .get_base_data = (NfcDeviceGetBaseData)ntag4xx_get_base_data, +}; + +Ntag4xxData* ntag4xx_alloc(void) { + Ntag4xxData* data = malloc(sizeof(Ntag4xxData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->device_name = furi_string_alloc(); + return data; +} + +void ntag4xx_free(Ntag4xxData* data) { + furi_check(data); + + ntag4xx_reset(data); + iso14443_4a_free(data->iso14443_4a_data); + furi_string_free(data->device_name); + free(data); +} + +void ntag4xx_reset(Ntag4xxData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(Ntag4xxVersion)); +} + +void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other) { + furi_check(data); + furi_check(other); + + ntag4xx_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; +} + +bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!ntag4xx_version_load(&data->version, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, NTAG4XX_PROTOCOL_NAME " specific data")) break; + if(!ntag4xx_version_save(&data->version, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + memcmp(&data->version, &other->version, sizeof(Ntag4xxVersion)) == 0; +} + +Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version) { + Ntag4xxType type = Ntag4xxTypeUnknown; + + switch(version->hw_major) { + case NTAG4XX_HW_MAJOR_TYPE_413_DNA: + type = Ntag4xxType413DNA; + break; + case NTAG4XX_HW_MAJOR_TYPE_424_DNA: + if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) { + type = Ntag4xxType424DNATT; + } else { + type = Ntag4xxType424DNA; + } + break; + // TODO: there is no info online or in other implementations (NXP TagInfo, NFC Tools, Proxmark3) + // about what the HWMajorVersion is supposed to be for NTAG426Q DNA, and they don't seem to be for sale + // case NTAG4XX_HW_MAJOR_TYPE_426Q_DNA: + // if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) { + // type = Ntag4xxType426QDNATT; + // } else { + // type = Ntag4xxType426QDNA; + // } + // break; + default: + break; + } + + return type; +} + +const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type) { + furi_check(data); + + const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version); + + if(type == Ntag4xxTypeUnknown) { + furi_string_printf(data->device_name, "Unknown %s", NTAG4XX_PROTOCOL_NAME); + } else { + furi_string_printf(data->device_name, "%s", ntag4xx_type_strings[type]); + if(name_type == NfcDeviceNameTypeShort) { + furi_string_replace(data->device_name, "TagTamper", "TT"); + } + } + + return furi_string_get_cstr(data->device_name); +} + +const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool ntag4xx_set_uid(Ntag4xxData* 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* ntag4xx_get_base_data(const Ntag4xxData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx.h b/lib/nfc/protocols/ntag4xx/ntag4xx.h new file mode 100644 index 000000000..56e5fbd65 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx.h @@ -0,0 +1,114 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NTAG4XX_UID_SIZE (7) +#define NTAG4XX_BATCH_SIZE (4) +#define NTAG4XX_BATCH_EXTRA_BITS 4 +#define NTAG4XX_FAB_KEY_SIZE_BITS_4 4 +#define NTAG4XX_FAB_KEY_SIZE_BITS_1 1 +#define NTAG4XX_PROD_WEEK_SIZE_BITS 7 + +#define NTAG4XX_CMD_GET_VERSION (0x60) + +typedef enum { + Ntag4xxErrorNone, + Ntag4xxErrorNotPresent, + Ntag4xxErrorProtocol, + Ntag4xxErrorTimeout, +} Ntag4xxError; + +typedef enum { + Ntag4xxType413DNA, + Ntag4xxType424DNA, + Ntag4xxType424DNATT, + Ntag4xxType426QDNA, + Ntag4xxType426QDNATT, + + Ntag4xxTypeUnknown, + Ntag4xxTypeNum, +} Ntag4xxType; + +#pragma pack(push, 1) +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[NTAG4XX_UID_SIZE]; + // [36b batch][5b fab key][7b prod week] + // 5b fab key is split 4b in last byte of batch and 1b in prod week + // Due to endianness, they appear swapped in the struct definition + uint8_t batch[NTAG4XX_BATCH_SIZE]; + struct { + uint8_t fab_key_4b : NTAG4XX_FAB_KEY_SIZE_BITS_4; + uint8_t batch_extra : NTAG4XX_BATCH_EXTRA_BITS; + }; + struct { + uint8_t prod_week : NTAG4XX_PROD_WEEK_SIZE_BITS; + uint8_t fab_key_1b : NTAG4XX_FAB_KEY_SIZE_BITS_1; + }; + uint8_t prod_year; + struct { + uint8_t fab_key_id; + } optional; +} Ntag4xxVersion; +#pragma pack(pop) + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + Ntag4xxVersion version; + FuriString* device_name; +} Ntag4xxData; + +extern const NfcDeviceBase nfc_device_ntag4xx; + +// Virtual methods + +Ntag4xxData* ntag4xx_alloc(void); + +void ntag4xx_free(Ntag4xxData* data); + +void ntag4xx_reset(Ntag4xxData* data); + +void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other); + +bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type); + +bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version); + +bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff); + +bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other); + +const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type); + +const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len); + +bool ntag4xx_set_uid(Ntag4xxData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* ntag4xx_get_base_data(const Ntag4xxData* data); + +// Helpers + +Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_i.c b/lib/nfc/protocols/ntag4xx/ntag4xx_i.c new file mode 100644 index 000000000..b4ee3a9eb --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_i.c @@ -0,0 +1,54 @@ +#include "ntag4xx_i.h" + +#define TAG "Ntag4xx" + +#define NTAG4XX_FFF_VERSION_KEY \ + NTAG4XX_FFF_PICC_PREFIX " " \ + "Version" + +Ntag4xxError ntag4xx_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return Ntag4xxErrorNone; + case Iso14443_4aErrorNotPresent: + return Ntag4xxErrorNotPresent; + case Iso14443_4aErrorTimeout: + return Ntag4xxErrorTimeout; + default: + return Ntag4xxErrorProtocol; + } +} + +Ntag4xxError ntag4xx_process_status_code(uint8_t status_code) { + switch(status_code) { + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: + return Ntag4xxErrorNone; + default: + return Ntag4xxErrorProtocol; + } +} + +bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf) { + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const bool can_parse = buf_size == sizeof(Ntag4xxVersion) || + buf_size == sizeof(Ntag4xxVersion) - sizeof(data->optional); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(Ntag4xxVersion)); + if(buf_size < sizeof(Ntag4xxVersion)) { + memset(&data->optional, 0, sizeof(data->optional)); + } + } + + return can_parse && (data->hw_type & 0x0F) == 0x04; +} + +bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, NTAG4XX_FFF_VERSION_KEY, (uint8_t*)data, sizeof(Ntag4xxVersion)); +} + +bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, NTAG4XX_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(Ntag4xxVersion)); +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_i.h b/lib/nfc/protocols/ntag4xx/ntag4xx_i.h new file mode 100644 index 000000000..5a71cf05a --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_i.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ntag4xx.h" + +#include + +#define NTAG4XX_FFF_PICC_PREFIX "PICC" + +// Internal helpers + +Ntag4xxError ntag4xx_process_error(Iso14443_4aError error); + +Ntag4xxError ntag4xx_process_status_code(uint8_t status_code); + +// Parse internal Ntag4xx structures + +bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf); + +// Load internal Ntag4xx structures + +bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff); + +// Save internal Ntag4xx structures + +bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c new file mode 100644 index 000000000..38e6f19d4 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c @@ -0,0 +1,165 @@ +#include "ntag4xx_poller_i.h" + +#include + +#include + +#define TAG "Ntag4xxPoller" + +#define NTAG4XX_BUF_SIZE (64U) +#define NTAG4XX_RESULT_BUF_SIZE (512U) + +typedef NfcCommand (*Ntag4xxPollerReadHandler)(Ntag4xxPoller* instance); + +static const Ntag4xxData* ntag4xx_poller_get_data(Ntag4xxPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Ntag4xxPoller* ntag4xx_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Ntag4xxPoller* instance = malloc(sizeof(Ntag4xxPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = ntag4xx_alloc(); + instance->tx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(NTAG4XX_RESULT_BUF_SIZE); + + instance->ntag4xx_event.data = &instance->ntag4xx_event_data; + + instance->general_event.protocol = NfcProtocolNtag4xx; + instance->general_event.event_data = &instance->ntag4xx_event; + instance->general_event.instance = instance; + + return instance; +} + +static void ntag4xx_poller_free(Ntag4xxPoller* instance) { + furi_assert(instance); + + ntag4xx_free(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); + free(instance); +} + +static NfcCommand ntag4xx_poller_handler_idle(Ntag4xxPoller* 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 = Ntag4xxPollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand ntag4xx_poller_handler_read_version(Ntag4xxPoller* instance) { + instance->error = ntag4xx_poller_read_version(instance, &instance->data->version); + if(instance->error == Ntag4xxErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->state = Ntag4xxPollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = Ntag4xxPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand ntag4xx_poller_handler_read_failed(Ntag4xxPoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed; + instance->ntag4xx_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Ntag4xxPollerStateIdle; + return command; +} + +static NfcCommand ntag4xx_poller_handler_read_success(Ntag4xxPoller* instance) { + FURI_LOG_D(TAG, "Read success"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Ntag4xxPollerReadHandler ntag4xx_poller_read_handler[Ntag4xxPollerStateNum] = { + [Ntag4xxPollerStateIdle] = ntag4xx_poller_handler_idle, + [Ntag4xxPollerStateReadVersion] = ntag4xx_poller_handler_read_version, + [Ntag4xxPollerStateReadFailed] = ntag4xx_poller_handler_read_failed, + [Ntag4xxPollerStateReadSuccess] = ntag4xx_poller_handler_read_success, +}; + +static void ntag4xx_poller_set_callback( + Ntag4xxPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand ntag4xx_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Ntag4xxPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = ntag4xx_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool ntag4xx_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Ntag4xxPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + do { + Ntag4xxError error = ntag4xx_poller_read_version(instance, &instance->data->version); + if(error != Ntag4xxErrorNone) break; + + protocol_detected = true; + } while(false); + } + + return protocol_detected; +} + +const NfcPollerBase ntag4xx_poller = { + .alloc = (NfcPollerAlloc)ntag4xx_poller_alloc, + .free = (NfcPollerFree)ntag4xx_poller_free, + .set_callback = (NfcPollerSetCallback)ntag4xx_poller_set_callback, + .run = (NfcPollerRun)ntag4xx_poller_run, + .detect = (NfcPollerDetect)ntag4xx_poller_detect, + .get_data = (NfcPollerGetData)ntag4xx_poller_get_data, +}; diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h new file mode 100644 index 000000000..ce7adc785 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h @@ -0,0 +1,43 @@ +#pragma once + +#include "ntag4xx.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Ntag4xxPoller opaque type definition. + */ +typedef struct Ntag4xxPoller Ntag4xxPoller; + +/** + * @brief Enumeration of possible Ntag4xx poller event types. + */ +typedef enum { + Ntag4xxPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Ntag4xxPollerEventTypeReadFailed, /**< Poller failed to read card. */ +} Ntag4xxPollerEventType; + +/** + * @brief Ntag4xx poller event data. + */ +typedef union { + Ntag4xxError error; /**< Error code indicating card reading fail reason. */ +} Ntag4xxPollerEventData; + +/** + * @brief Ntag4xx poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Ntag4xxPollerEventType type; /**< Type of emmitted event. */ + Ntag4xxPollerEventData* data; /**< Pointer to event specific data. */ +} Ntag4xxPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h new file mode 100644 index 000000000..ac0cdce9b --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase ntag4xx_poller; diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c new file mode 100644 index 000000000..5b8b7191d --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c @@ -0,0 +1,52 @@ +#include "ntag4xx_poller_i.h" + +#include + +#include "ntag4xx_i.h" + +#define TAG "Ntag4xxPoller" + +Ntag4xxError ntag4xx_poller_send_chunks( + Ntag4xxPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_check(instance); + + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + NxpNativeCommandModeIsoWrapped, + instance->tx_buffer, + instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return ntag4xx_process_error(iso14443_4a_error); + } + + return ntag4xx_process_status_code(status_code); +} + +Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data) { + furi_check(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, NTAG4XX_CMD_GET_VERSION); + + Ntag4xxError error; + + do { + error = + ntag4xx_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != Ntag4xxErrorNone) break; + + if(!ntag4xx_version_parse(data, instance->result_buffer)) { + error = Ntag4xxErrorProtocol; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h new file mode 100644 index 000000000..b77e694ef --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h @@ -0,0 +1,40 @@ +#pragma once + +#include "ntag4xx_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Ntag4xxPollerStateIdle, + Ntag4xxPollerStateReadVersion, + Ntag4xxPollerStateReadFailed, + Ntag4xxPollerStateReadSuccess, + + Ntag4xxPollerStateNum, +} Ntag4xxPollerState; + +struct Ntag4xxPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Ntag4xxPollerState state; + Ntag4xxError error; + Ntag4xxData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + Ntag4xxPollerEventData ntag4xx_event_data; + Ntag4xxPollerEvent ntag4xx_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.c b/lib/nfc/protocols/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..03527b844 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.c @@ -0,0 +1,170 @@ +#include "type_4_tag_i.h" + +#define TYPE_4_TAG_PROTOCOL_NAME "Type 4 Tag" + +const NfcDeviceBase nfc_device_type_4_tag = { + .protocol_name = TYPE_4_TAG_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)type_4_tag_alloc, + .free = (NfcDeviceFree)type_4_tag_free, + .reset = (NfcDeviceReset)type_4_tag_reset, + .copy = (NfcDeviceCopy)type_4_tag_copy, + .verify = (NfcDeviceVerify)type_4_tag_verify, + .load = (NfcDeviceLoad)type_4_tag_load, + .save = (NfcDeviceSave)type_4_tag_save, + .is_equal = (NfcDeviceEqual)type_4_tag_is_equal, + .get_name = (NfcDeviceGetName)type_4_tag_get_device_name, + .get_uid = (NfcDeviceGetUid)type_4_tag_get_uid, + .set_uid = (NfcDeviceSetUid)type_4_tag_set_uid, + .get_base_data = (NfcDeviceGetBaseData)type_4_tag_get_base_data, +}; + +Type4TagData* type_4_tag_alloc(void) { + Type4TagData* data = malloc(sizeof(Type4TagData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->device_name = furi_string_alloc(); + data->platform_name = furi_string_alloc(); + data->ndef_data = simple_array_alloc(&simple_array_config_uint8_t); + return data; +} + +void type_4_tag_free(Type4TagData* data) { + furi_check(data); + + type_4_tag_reset(data); + simple_array_free(data->ndef_data); + furi_string_free(data->platform_name); + furi_string_free(data->device_name); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void type_4_tag_reset(Type4TagData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + data->is_tag_specific = false; + furi_string_reset(data->device_name); + furi_string_reset(data->platform_name); + data->t4t_version.value = 0; + data->chunk_max_read = 0; + data->chunk_max_write = 0; + data->ndef_file_id = 0; + data->ndef_max_len = 0; + data->ndef_read_lock = 0; + data->ndef_write_lock = 0; + + simple_array_reset(data->ndef_data); +} + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + type_4_tag_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->is_tag_specific = other->is_tag_specific; + furi_string_set(data->device_name, other->device_name); + furi_string_set(data->platform_name, other->platform_name); + data->t4t_version.value = other->t4t_version.value; + data->chunk_max_read = other->chunk_max_read; + data->chunk_max_write = other->chunk_max_write; + data->ndef_file_id = other->ndef_file_id; + data->ndef_max_len = other->ndef_max_len; + data->ndef_read_lock = other->ndef_read_lock; + data->ndef_write_lock = other->ndef_write_lock; + + simple_array_copy(data->ndef_data, other->ndef_data); +} + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!type_4_tag_ndef_data_load(data, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, TYPE_4_TAG_PROTOCOL_NAME " specific data")) + break; + if(!type_4_tag_ndef_data_save(data, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + data->is_tag_specific == other->is_tag_specific && + data->t4t_version.value == other->t4t_version.value && + data->chunk_max_read == other->chunk_max_read && + data->chunk_max_write == other->chunk_max_write && + data->ndef_file_id == other->ndef_file_id && + data->ndef_max_len == other->ndef_max_len && + data->ndef_read_lock == other->ndef_read_lock && + data->ndef_write_lock == other->ndef_write_lock && + simple_array_is_equal(data->ndef_data, other->ndef_data); +} + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return TYPE_4_TAG_PROTOCOL_NAME; +} + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool type_4_tag_set_uid(Type4TagData* 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* type_4_tag_get_base_data(const Type4TagData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.h b/lib/nfc/protocols/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..29c41a5be --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE (2048U - sizeof(uint16_t)) + +typedef enum { + Type4TagErrorNone, + Type4TagErrorNotPresent, + Type4TagErrorProtocol, + Type4TagErrorTimeout, + Type4TagErrorWrongFormat, + Type4TagErrorNotSupported, + Type4TagErrorApduFailed, + Type4TagErrorCardUnformatted, + Type4TagErrorCardLocked, + Type4TagErrorCustomCommand, +} Type4TagError; + +typedef enum { + Type4TagPlatformUnknown, + Type4TagPlatformNtag4xx, + Type4TagPlatformMfDesfire, +} Type4TagPlatform; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + FuriString* device_name; + // Tag specific data + bool is_tag_specific; + Type4TagPlatform platform; + FuriString* platform_name; + union { + struct { + uint8_t minor : 4; + uint8_t major : 4; + }; + uint8_t value; + } t4t_version; + uint16_t chunk_max_read; + uint16_t chunk_max_write; + uint16_t ndef_file_id; + uint16_t ndef_max_len; + uint8_t ndef_read_lock; + uint8_t ndef_write_lock; + // Data contained, not tag specific + SimpleArray* ndef_data; +} Type4TagData; + +extern const NfcDeviceBase nfc_device_type_4_tag; + +// Virtual methods + +Type4TagData* type_4_tag_alloc(void); + +void type_4_tag_free(Type4TagData* data); + +void type_4_tag_reset(Type4TagData* data); + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other); + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type); + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version); + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff); + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other); + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type); + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len); + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c new file mode 100644 index 000000000..69e9aadf9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c @@ -0,0 +1,163 @@ +#include "type_4_tag_i.h" + +#include + +#define TAG "Type4Tag" + +#define TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY "NDEF Data Size" +#define TYPE_4_TAG_FFF_NDEF_DATA_KEY "NDEF Data" + +#define TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE (16U) + +const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_MF_NAME}; +const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_DF_NAME}; + +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; + } +} + +void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len) { + furi_check(len >= TYPE_4_TAG_T4T_CC_MIN_SIZE); + Type4TagCc* cc = (Type4TagCc*)buf; + + bit_lib_num_to_bytes_be(TYPE_4_TAG_T4T_CC_MIN_SIZE, sizeof(cc->len), (void*)&cc->len); + cc->t4t_vno = TYPE_4_TAG_T4T_CC_VNO; + bit_lib_num_to_bytes_be( + data->is_tag_specific ? MIN(data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN, + sizeof(cc->mle), + (void*)&cc->mle); + bit_lib_num_to_bytes_be( + data->is_tag_specific ? MIN(data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_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( + data->is_tag_specific ? data->ndef_file_id : TYPE_4_TAG_T4T_NDEF_EF_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( + sizeof(uint16_t) + + (data->is_tag_specific ? data->ndef_max_len : TYPE_4_TAG_DEFAULT_NDEF_SIZE), + 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 = + data->is_tag_specific ? data->ndef_read_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + cc->tlv[0].value.ndef_file_ctrl.write_perm = + data->is_tag_specific ? data->ndef_write_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; +} + +Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len) { + if(len < TYPE_4_TAG_T4T_CC_MIN_SIZE) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + return Type4TagErrorWrongFormat; + } + + const Type4TagCc* cc = (const Type4TagCc*)buf; + if(cc->t4t_vno != TYPE_4_TAG_T4T_CC_VNO) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + return Type4TagErrorNotSupported; + } + + const Type4TagCcTlv* tlv = cc->tlv; + const Type4TagCcTlvNdefFileCtrl* ndef_file_ctrl = NULL; + const void* end = MIN((void*)cc + cc->len, (void*)cc + len); + while((void*)tlv < end) { + if(tlv->type == Type4TagCcTlvTypeNdefFileCtrl) { + ndef_file_ctrl = &tlv->value.ndef_file_ctrl; + break; + } + + if(tlv->len < 0xFF) { + tlv = (void*)&tlv->value + tlv->len; + } else { + uint16_t len = bit_lib_bytes_to_num_be((void*)&tlv->len + 1, sizeof(uint16_t)); + tlv = (void*)&tlv->value + sizeof(len) + len; + } + } + if(!ndef_file_ctrl) { + FURI_LOG_E(TAG, "No NDEF file ctrl TLV"); + return Type4TagErrorWrongFormat; + } + + data->t4t_version.value = cc->t4t_vno; + data->chunk_max_read = bit_lib_bytes_to_num_be((void*)&cc->mle, sizeof(cc->mle)); + data->chunk_max_write = bit_lib_bytes_to_num_be((void*)&cc->mlc, sizeof(cc->mlc)); + data->ndef_file_id = + bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->file_id, sizeof(ndef_file_ctrl->file_id)); + data->ndef_max_len = + bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->max_len, sizeof(ndef_file_ctrl->max_len)) - + sizeof(uint16_t); + data->ndef_read_lock = ndef_file_ctrl->read_perm; + data->ndef_write_lock = ndef_file_ctrl->write_perm; + + return Type4TagErrorNone; +} + +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)) { + return false; + } + if(ndef_data_size == 0) { + return true; + } + + simple_array_init(data->ndef_data, ndef_data_size); + + uint32_t ndef_data_pos = 0; + uint8_t* ndef_data = simple_array_get_data(data->ndef_data); + while(ndef_data_size > 0) { + uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE); + + if(!flipper_format_read_hex( + ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) { + simple_array_reset(data->ndef_data); + return false; + } + + ndef_data_pos += ndef_line_size; + ndef_data_size -= ndef_line_size; + } + + return true; +} + +bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff) { + uint32_t ndef_data_size = simple_array_get_count(data->ndef_data); + if(!flipper_format_write_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) { + return false; + } + if(ndef_data_size == 0) { + return true; + } + + uint32_t ndef_data_pos = 0; + uint8_t* ndef_data = simple_array_get_data(data->ndef_data); + while(ndef_data_size > 0) { + uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE); + + if(!flipper_format_write_hex( + ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) { + return false; + } + + ndef_data_pos += ndef_line_size; + ndef_data_size -= ndef_line_size; + } + + return true; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h new file mode 100644 index 000000000..f47d75236 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h @@ -0,0 +1,110 @@ +#pragma once + +#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_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_ID_MASK (1 << 7) +#define TYPE_4_TAG_ISO_READ_P_BEGINNING 0x00, 0x00 +#define TYPE_4_TAG_ISO_READ_P_OFFSET_MAX (32767U) +#define TYPE_4_TAG_ISO_READ_LE_FULL (0x00) + +// ISO UPDATE BINARY command and parameters +#define TYPE_4_TAG_ISO_WRITE_CMD 0x00, 0xD6 +#define TYPE_4_TAG_ISO_WRITE_P1_ID_MASK (1 << 7) +#define TYPE_4_TAG_ISO_WRITE_P_BEGINNING 0x00, 0x00 +#define TYPE_4_TAG_ISO_WRITE_LE_EMPTY (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_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) + +// Common IDs and Names, note: +// MF = Master File (PICC/Card Level) +// DF = Dedicated File (Application) +// EF = Elementary File (File) +#define TYPE_4_TAG_ISO_NAME_LEN (7U) +#define TYPE_4_TAG_ISO_MF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x00 +#define TYPE_4_TAG_ISO_DF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 +#define TYPE_4_TAG_ISO_ID_LEN (2U) +#define TYPE_4_TAG_ISO_MF_ID (0x3F00) +#define TYPE_4_TAG_ISO_DF_ID (0xE110) +#define TYPE_4_TAG_T4T_CC_EF_ID (0xE103) +#define TYPE_4_TAG_T4T_NDEF_EF_ID (0xE104) + +// Capability Container parsing parameters +#define TYPE_4_TAG_T4T_CC_VNO (0x20) +#define TYPE_4_TAG_T4T_CC_RW_LOCK_NONE (0x00) +#define TYPE_4_TAG_T4T_CC_MIN_SIZE (sizeof(Type4TagCc) + sizeof(Type4TagCcTlv)) + +// Implementation-specific sizes and defaults +// 4a layer adds 1..3 byte prefix, 3a layer adds 2 byte suffix and has 256 byte buffer +#define TYPE_4_TAG_BUF_SIZE (256U - 3U - 2U) +// Read returns 2 byte status trailer, write sends 5 byte command header +#define TYPE_4_TAG_CHUNK_LEN MIN(TYPE_4_TAG_BUF_SIZE - 5U, TYPE_4_TAG_ISO_RW_CHUNK_LEN) +#define TYPE_4_TAG_DEFAULT_NDEF_SIZE TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE + +extern const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN]; +extern const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN]; + +// Capability Container parsing structures + +typedef enum FURI_PACKED { + Type4TagCcTlvTypeNdefFileCtrl = 0x04, + Type4TagCcTlvTypeProprietaryFileCtrl = 0x05, +} Type4TagCcTlvType; + +typedef struct FURI_PACKED { + uint16_t file_id; + uint16_t max_len; + uint8_t read_perm; + uint8_t write_perm; +} Type4TagCcTlvNdefFileCtrl; + +typedef union FURI_PACKED { + Type4TagCcTlvNdefFileCtrl ndef_file_ctrl; +} Type4TagCcTlvValue; + +typedef struct FURI_PACKED { + Type4TagCcTlvType type; + uint8_t len; + Type4TagCcTlvValue value; +} Type4TagCcTlv; + +typedef struct FURI_PACKED { + uint16_t len; + uint8_t t4t_vno; + uint16_t mle; + uint16_t mlc; + Type4TagCcTlv tlv[]; +} Type4TagCc; + +// Internal helpers + +Type4TagError type_4_tag_process_error(Iso14443_4aError error); + +void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len); + +Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len); + +// Load internal Type4Tag structures + +bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff); + +// Save internal Type4Tag structures + +bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c new file mode 100644 index 000000000..58e94470d --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c @@ -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, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h new file mode 100644 index 000000000..7e0301322 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h @@ -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 diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h new file mode 100644 index 000000000..87206fe6b --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_type_4_tag; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c new file mode 100644 index 000000000..7da6fadc9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c @@ -0,0 +1,382 @@ +#include "type_4_tag_listener_i.h" +#include "type_4_tag_i.h" + +#include + +#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_NAME && lc == TYPE_4_TAG_ISO_NAME_LEN) { + if(memcmp(type_4_tag_iso_mf_name, data, sizeof(type_4_tag_iso_mf_name)) == 0) { + instance->state = Type4TagListenerStateSelectedPicc; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(memcmp(type_4_tag_iso_df_name, data, sizeof(type_4_tag_iso_df_name)) == 0) { + instance->state = Type4TagListenerStateSelectedApplication; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + } else if( + (p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID || p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID) && + lc == TYPE_4_TAG_ISO_ID_LEN) { + uint16_t id = bit_lib_bytes_to_num_be(data, sizeof(uint16_t)); + + if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID) { + if(id == TYPE_4_TAG_ISO_MF_ID) { + instance->state = Type4TagListenerStateSelectedPicc; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(id == TYPE_4_TAG_ISO_DF_ID) { + instance->state = Type4TagListenerStateSelectedApplication; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + } + + if(instance->state >= Type4TagListenerStateSelectedApplication) { + if(id == TYPE_4_TAG_T4T_CC_EF_ID) { + instance->state = Type4TagListenerStateSelectedCapabilityContainer; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(id == (instance->data->is_tag_specific ? instance->data->ndef_file_id : + TYPE_4_TAG_T4T_NDEF_EF_ID)) { + instance->state = Type4TagListenerStateSelectedNdefMessage; + 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 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[TYPE_4_TAG_T4T_CC_MIN_SIZE]; + 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; + } + type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf)); + + 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 = simple_array_get_count(instance->data->ndef_data); + 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 Type4TagError type_4_tag_listener_iso_write( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(le); + + size_t offset; + if(p1 & TYPE_4_TAG_ISO_WRITE_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) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorNotSupported; + } + + if(instance->state == Type4TagListenerStateSelectedNdefMessage) { + if(offset + lc > sizeof(uint16_t) + (instance->data->is_tag_specific ? + instance->data->ndef_max_len : + TYPE_4_TAG_DEFAULT_NDEF_SIZE)) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + + const size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data); + size_t ndef_file_len_new = ndef_file_len; + if(offset < sizeof(uint16_t)) { + const uint8_t write_len = sizeof(uint16_t) - offset; + ndef_file_len_new = bit_lib_bytes_to_num_be(data, write_len); + offset = sizeof(uint16_t); + data += offset; + lc -= write_len; + } + offset -= sizeof(uint16_t); + + ndef_file_len_new = MAX(ndef_file_len_new, offset + lc); + if(ndef_file_len_new != ndef_file_len) { + SimpleArray* ndef_data_temp = simple_array_alloc(&simple_array_config_uint8_t); + if(ndef_file_len_new > 0) { + simple_array_init(ndef_data_temp, ndef_file_len_new); + if(ndef_file_len > 0) { + memcpy( + simple_array_get_data(ndef_data_temp), + simple_array_get_data(instance->data->ndef_data), + MIN(ndef_file_len_new, ndef_file_len)); + } + } + simple_array_copy(instance->data->ndef_data, ndef_data_temp); + simple_array_free(ndef_data_temp); + } + + if(ndef_file_len_new > 0 && lc > 0) { + uint8_t* ndef_data = simple_array_get_data(instance->data->ndef_data); + memcpy(&ndef_data[offset], data, lc); + } + 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, + }, + { + .cla_ins = {TYPE_4_TAG_ISO_WRITE_CMD}, + .handler = type_4_tag_listener_iso_write, + }, +}; + +Type4TagError + type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer) { + Type4TagError error = Type4TagErrorNone; + + bit_buffer_reset(instance->tx_buffer); + + 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; + } + } + + error = handler(instance, apdu->p1, apdu->p2, lc, data, le); + } while(false); + + if(bit_buffer_get_size_bytes(instance->tx_buffer) > 0) { + const Iso14443_4aError iso14443_4a_error = + iso14443_4a_listener_send_block(instance->iso14443_4a_listener, instance->tx_buffer); + + // Keep error flag to show unknown command on screen + if(error != Type4TagErrorCustomCommand) { + error = type_4_tag_process_error(iso14443_4a_error); + } + } + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h new file mode 100644 index 000000000..fd602c974 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h @@ -0,0 +1,38 @@ +#pragma once + +#include "type_4_tag_listener.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagListenerStateIdle, + Type4TagListenerStateSelectedPicc, + 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 diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c new file mode 100644 index 000000000..0291d2df9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c @@ -0,0 +1,289 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_poller_defs.h" +#include "type_4_tag_i.h" + +#define TAG "Type4TagPoller" + +typedef NfcCommand (*Type4TagPollerReadHandler)(Type4TagPoller* instance); + +static const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Type4TagPoller* type_4_tag_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Type4TagPoller* instance = malloc(sizeof(Type4TagPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = type_4_tag_alloc(); + instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + + instance->type_4_tag_event.data = &instance->type_4_tag_event_data; + + instance->general_event.protocol = NfcProtocolType4Tag; + instance->general_event.event_data = &instance->type_4_tag_event; + instance->general_event.instance = instance; + + return instance; +} + +static void type_4_tag_poller_free(Type4TagPoller* instance) { + furi_assert(instance); + + type_4_tag_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand type_4_tag_poller_handler_idle(Type4TagPoller* instance) { + 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 = Type4TagPollerStateRequestMode; + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_request_mode(Type4TagPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->type_4_tag_event.type = Type4TagPollerEventTypeRequestMode; + instance->type_4_tag_event.data->poller_mode.mode = Type4TagPollerModeRead; + instance->type_4_tag_event.data->poller_mode.data = NULL; + + command = instance->callback(instance->general_event, instance->context); + instance->mode = instance->type_4_tag_event.data->poller_mode.mode; + if(instance->mode == Type4TagPollerModeWrite) { + type_4_tag_copy(instance->data, instance->type_4_tag_event.data->poller_mode.data); + } + + instance->state = Type4TagPollerStateDetectPlatform; + return command; +} + +static NfcCommand type_4_tag_poller_handler_detect_platform(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_detect_platform(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Detect platform success"); + } else { + FURI_LOG_W(TAG, "Failed to detect platform"); + } + instance->state = Type4TagPollerStateSelectApplication; + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_select_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_select_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Select application success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to select application"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateApplication; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read CC success"); + instance->state = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerStateReadNdefMessage : + Type4TagPollerStateWriteNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to read CC"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateCapabilityContainer; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read NDEF success"); + instance->state = Type4TagPollerStateSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read NDEF"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create application success"); + instance->state = Type4TagPollerStateSelectApplication; + } else { + FURI_LOG_E(TAG, "Failed to create application"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create CC success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to create CC"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create NDEF success"); + instance->state = Type4TagPollerStateWriteNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to create NDEF"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_write_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_write_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Write NDEF success"); + instance->state = Type4TagPollerStateSuccess; + } else { + FURI_LOG_E(TAG, "Failed to write NDEF"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateNdefMessage; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_failed(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Operation Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerEventTypeReadFailed : + Type4TagPollerEventTypeWriteFail; + instance->type_4_tag_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Type4TagPollerStateIdle; + return command; +} + +static NfcCommand type_4_tag_poller_handler_success(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Operation succeeded"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerEventTypeReadSuccess : + Type4TagPollerEventTypeWriteSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Type4TagPollerReadHandler type_4_tag_poller_read_handler[Type4TagPollerStateNum] = { + [Type4TagPollerStateIdle] = type_4_tag_poller_handler_idle, + [Type4TagPollerStateRequestMode] = type_4_tag_poller_handler_request_mode, + [Type4TagPollerStateDetectPlatform] = type_4_tag_poller_handler_detect_platform, + [Type4TagPollerStateSelectApplication] = type_4_tag_poller_handler_select_app, + [Type4TagPollerStateReadCapabilityContainer] = type_4_tag_poller_handler_read_cc, + [Type4TagPollerStateReadNdefMessage] = type_4_tag_poller_handler_read_ndef, + [Type4TagPollerStateCreateApplication] = type_4_tag_poller_handler_create_app, + [Type4TagPollerStateCreateCapabilityContainer] = type_4_tag_poller_handler_create_cc, + [Type4TagPollerStateCreateNdefMessage] = type_4_tag_poller_handler_create_ndef, + [Type4TagPollerStateWriteNdefMessage] = type_4_tag_poller_handler_write_ndef, + [Type4TagPollerStateFailed] = type_4_tag_poller_handler_failed, + [Type4TagPollerStateSuccess] = type_4_tag_poller_handler_success, +}; + +static void type_4_tag_poller_set_callback( + Type4TagPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand type_4_tag_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = type_4_tag_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->type_4_tag_event.type = Type4TagPollerEventTypeReadFailed; + instance->type_4_tag_event.data->error = + type_4_tag_process_error(iso14443_4a_event->data->error); + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool type_4_tag_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + Type4TagError error = type_4_tag_poller_select_app(instance); + if(error == Type4TagErrorNone) { + protocol_detected = true; + } + } + + return protocol_detected; +} + +const NfcPollerBase type_4_tag_poller = { + .alloc = (NfcPollerAlloc)type_4_tag_poller_alloc, + .free = (NfcPollerFree)type_4_tag_poller_free, + .set_callback = (NfcPollerSetCallback)type_4_tag_poller_set_callback, + .run = (NfcPollerRun)type_4_tag_poller_run, + .detect = (NfcPollerDetect)type_4_tag_poller_detect, + .get_data = (NfcPollerGetData)type_4_tag_poller_get_data, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h new file mode 100644 index 000000000..5ac4fd0f2 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h @@ -0,0 +1,63 @@ +#pragma once + +#include "type_4_tag.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type4TagPoller opaque type definition. + */ +typedef struct Type4TagPoller Type4TagPoller; + +/** + * @brief Enumeration of possible Type4Tag poller event types. + */ +typedef enum { + Type4TagPollerEventTypeRequestMode, /**< Poller requests for operating mode. */ + Type4TagPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Type4TagPollerEventTypeReadFailed, /**< Poller failed to read card. */ + Type4TagPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ + Type4TagPollerEventTypeWriteFail, /**< Poller failed to write card. */ +} Type4TagPollerEventType; + +/** + * @brief Enumeration of possible Type4Tag poller operating modes. + */ +typedef enum { + Type4TagPollerModeRead, /**< Poller will only read card. It's a default mode. */ + Type4TagPollerModeWrite, /**< Poller will write already saved card to another presented card. */ +} Type4TagPollerMode; + +/** + * @brief Type4Tag poller request mode event data. + * + * This instance of this structure must be filled on Type4TagPollerEventTypeRequestMode event. + */ +typedef struct { + Type4TagPollerMode mode; /**< Mode to be used by poller. */ + const Type4TagData* data; /**< Data to be used by poller. */ +} Type4TagPollerEventDataRequestMode; + +/** + * @brief Type4Tag poller event data. + */ +typedef union { + Type4TagError error; /**< Error code indicating card reading fail reason. */ + Type4TagPollerEventDataRequestMode poller_mode; /**< Poller mode context. */ +} Type4TagPollerEventData; + +/** + * @brief Type4Tag poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Type4TagPollerEventType type; /**< Type of emmitted event. */ + Type4TagPollerEventData* data; /**< Pointer to event specific data. */ +} Type4TagPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h new file mode 100644 index 000000000..fe71b560a --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase type_4_tag_poller; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c new file mode 100644 index 000000000..52f3d6220 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c @@ -0,0 +1,501 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_i.h" + +#include + +#include +#include +#include +#include +#include + +#define TAG "Type4TagPoller" + +static const MfDesfireApplicationId mf_des_picc_app_id = {.data = {0x00, 0x00, 0x00}}; +static const MfDesfireApplicationId mf_des_t4t_app_id = {.data = {0x10, 0xEE, 0xEE}}; +static const MfDesfireKeySettings mf_des_t4t_app_key_settings = { + .is_master_key_changeable = true, + .is_free_directory_list = true, + .is_free_create_delete = true, + .is_config_changeable = true, + .change_key_id = 0, + .max_keys = 1, + .flags = 0, +}; +#define MF_DES_T4T_CC_FILE_ID (0x01) +static const MfDesfireFileSettings mf_des_t4t_cc_file = { + .type = MfDesfireFileTypeStandard, + .comm = MfDesfireFileCommunicationSettingsPlaintext, + .access_rights[0] = 0xEEEE, + .access_rights_len = 1, + .data.size = TYPE_4_TAG_T4T_CC_MIN_SIZE, +}; +#define MF_DES_T4T_NDEF_FILE_ID (0x02) +static const MfDesfireFileSettings mf_des_t4t_ndef_file_default = { + .type = MfDesfireFileTypeStandard, + .comm = MfDesfireFileCommunicationSettingsPlaintext, + .access_rights[0] = 0xEEE0, + .access_rights_len = 1, +}; + +Type4TagError type_4_tag_apdu_trx(Type4TagPoller* instance, BitBuffer* tx_buf, BitBuffer* rx_buf) { + furi_check(instance); + + bit_buffer_reset(rx_buf); + + Iso14443_4aError iso14443_4a_error = + iso14443_4a_poller_send_block(instance->iso14443_4a_poller, tx_buf, rx_buf); + + bit_buffer_reset(tx_buf); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return type_4_tag_process_error(iso14443_4a_error); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buf); + if(response_len < TYPE_4_TAG_ISO_STATUS_LEN) { + return Type4TagErrorWrongFormat; + } + + static const uint8_t success[TYPE_4_TAG_ISO_STATUS_LEN] = {TYPE_4_TAG_ISO_STATUS_SUCCESS}; + uint8_t status[TYPE_4_TAG_ISO_STATUS_LEN] = { + bit_buffer_get_byte(rx_buf, response_len - 2), + bit_buffer_get_byte(rx_buf, response_len - 1), + }; + bit_buffer_set_size_bytes(rx_buf, response_len - 2); + + if(memcmp(status, success, sizeof(status)) == 0) { + return Type4TagErrorNone; + } else { + FURI_LOG_E(TAG, "APDU failed: %02X%02X", status[0], status[1]); + return Type4TagErrorApduFailed; + } +} + +static Type4TagError type_4_tag_poller_iso_select_name( + Type4TagPoller* instance, + const uint8_t* name, + uint8_t name_len) { + static const uint8_t type_4_tag_iso_select_name_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_NAME, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_iso_select_name_apdu, + sizeof(type_4_tag_iso_select_name_apdu)); + bit_buffer_append_byte(instance->tx_buffer, name_len); + bit_buffer_append_bytes(instance->tx_buffer, name, name_len); + + Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted; + + return error; +} + +static Type4TagError + type_4_tag_poller_iso_select_file(Type4TagPoller* instance, uint16_t file_id) { + static const uint8_t type_4_tag_iso_select_file_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + sizeof(file_id), + }; + uint8_t file_id_be[sizeof(file_id)]; + bit_lib_num_to_bytes_be(file_id, sizeof(file_id), file_id_be); + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_iso_select_file_apdu, + sizeof(type_4_tag_iso_select_file_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, file_id_be, sizeof(file_id_be)); + + Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted; + + return error; +} + +static Type4TagError type_4_tag_poller_iso_read( + Type4TagPoller* instance, + uint16_t offset, + uint16_t length, + uint8_t* buffer) { + const uint8_t chunk_max = instance->data->is_tag_specific ? + MIN(instance->data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN; + if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) { + FURI_LOG_E(TAG, "File too large: %zu bytes", length); + return Type4TagErrorNotSupported; + } + + static const uint8_t type_4_tag_iso_read_apdu[] = { + TYPE_4_TAG_ISO_READ_CMD, + }; + + while(length > 0) { + uint8_t chunk_len = MIN(length, chunk_max); + uint8_t offset_be[sizeof(offset)]; + bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be); + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_iso_read_apdu, sizeof(type_4_tag_iso_read_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be)); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + + Type4TagError error = + type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) { + return error; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != chunk_len) { + FURI_LOG_E( + TAG, + "Wrong chunk len: %zu != %zu", + bit_buffer_get_size_bytes(instance->rx_buffer), + chunk_len); + return Type4TagErrorWrongFormat; + } + + memcpy(buffer, bit_buffer_get_data(instance->rx_buffer), chunk_len); + buffer += chunk_len; + offset += chunk_len; + length -= chunk_len; + } + + return Type4TagErrorNone; +} + +static Type4TagError type_4_tag_poller_iso_write( + Type4TagPoller* instance, + uint16_t offset, + uint16_t length, + uint8_t* buffer) { + const uint8_t chunk_max = instance->data->is_tag_specific ? + MIN(instance->data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN; + if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) { + FURI_LOG_E(TAG, "File too large: %zu bytes", length); + return Type4TagErrorNotSupported; + } + + static const uint8_t type_4_tag_iso_write_apdu[] = { + TYPE_4_TAG_ISO_WRITE_CMD, + }; + + while(length > 0) { + uint8_t chunk_len = MIN(length, chunk_max); + uint8_t offset_be[sizeof(offset)]; + bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be); + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_iso_write_apdu, sizeof(type_4_tag_iso_write_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be)); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + bit_buffer_append_bytes(instance->tx_buffer, buffer, chunk_len); + + Type4TagError error = + type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardLocked; + if(error != Type4TagErrorNone) { + return error; + } + + buffer += chunk_len; + offset += chunk_len; + length -= chunk_len; + } + + return Type4TagErrorNone; +} + +Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance) { + furi_check(instance); + + Iso14443_4aPollerEvent iso14443_4a_event = { + .type = Iso14443_4aPollerEventTypeReady, + .data = NULL, + }; + NfcGenericEvent event = { + .protocol = NfcProtocolIso14443_4a, + .instance = instance->iso14443_4a_poller, + .event_data = &iso14443_4a_event, + }; + + Type4TagPlatform platform = Type4TagPlatformUnknown; + NfcDevice* device = nfc_device_alloc(); + + do { + FURI_LOG_D(TAG, "Detect NTAG4xx"); + Ntag4xxPoller* ntag4xx = ntag4xx_poller.alloc(instance->iso14443_4a_poller); + if(ntag4xx_poller.detect(event, ntag4xx)) { + platform = Type4TagPlatformNtag4xx; + nfc_device_set_data(device, NfcProtocolNtag4xx, ntag4xx_poller.get_data(ntag4xx)); + } + ntag4xx_poller.free(ntag4xx); + if(platform != Type4TagPlatformUnknown) break; + + FURI_LOG_D(TAG, "Detect DESFire"); + MfDesfirePoller* mf_desfire = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_desfire, NxpNativeCommandModeIsoWrapped); + if(mf_desfire_poller.detect(event, mf_desfire)) { + platform = Type4TagPlatformMfDesfire; + nfc_device_set_data( + device, NfcProtocolMfDesfire, mf_desfire_poller.get_data(mf_desfire)); + } + mf_desfire_poller.free(mf_desfire); + if(platform != Type4TagPlatformUnknown) break; + } while(false); + + Type4TagError error; + if(platform != Type4TagPlatformUnknown) { + furi_string_set( + instance->data->platform_name, nfc_device_get_name(device, NfcDeviceNameTypeFull)); + error = Type4TagErrorNone; + } else { + furi_string_reset(instance->data->platform_name); + error = Type4TagErrorNotSupported; + } + instance->data->platform = platform; + nfc_device_free(device); + + return error; +} + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance) { + furi_check(instance); + + FURI_LOG_D(TAG, "Select application"); + return type_4_tag_poller_iso_select_name( + instance, type_4_tag_iso_df_name, sizeof(type_4_tag_iso_df_name)); +} + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select CC"); + error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read CC len"); + uint16_t cc_len; + uint8_t cc_len_be[sizeof(cc_len)]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_len_be), cc_len_be); + if(error != Type4TagErrorNone) break; + cc_len = bit_lib_bytes_to_num_be(cc_len_be, sizeof(cc_len_be)); + + FURI_LOG_D(TAG, "Read CC"); + uint8_t cc_buf[cc_len]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_buf), cc_buf); + if(error != Type4TagErrorNone) break; + + error = type_4_tag_cc_parse(instance->data, cc_buf, sizeof(cc_buf)); + if(error != Type4TagErrorNone) break; + instance->data->is_tag_specific = true; + + FURI_LOG_D(TAG, "Detected NDEF file ID %04X", instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read NDEF len"); + uint16_t ndef_len; + uint8_t ndef_len_be[sizeof(ndef_len)]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(ndef_len_be), ndef_len_be); + if(error != Type4TagErrorNone) break; + ndef_len = bit_lib_bytes_to_num_be(ndef_len_be, sizeof(ndef_len_be)); + + if(ndef_len == 0) { + FURI_LOG_D(TAG, "NDEF file is empty"); + break; + } + + FURI_LOG_D(TAG, "Read NDEF"); + simple_array_init(instance->data->ndef_data, ndef_len); + uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data); + error = type_4_tag_poller_iso_read(instance, sizeof(ndef_len), ndef_len, ndef_buf); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, "Read %hu bytes from NDEF file %04X", ndef_len, instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Select DESFire PICC"); + mf_des_error = mf_desfire_poller_select_application(mf_des, &mf_des_picc_app_id); + if(mf_des_error != MfDesfireErrorNone) { + error = Type4TagErrorProtocol; + break; + } + + FURI_LOG_D(TAG, "Create DESFire T4T app"); + mf_des_error = mf_desfire_poller_create_application( + mf_des, + &mf_des_t4t_app_id, + &mf_des_t4t_app_key_settings, + TYPE_4_TAG_ISO_DF_ID, + type_4_tag_iso_df_name, + sizeof(type_4_tag_iso_df_name)); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Create DESFire CC"); + mf_des_error = mf_desfire_poller_create_file( + mf_des, MF_DES_T4T_CC_FILE_ID, &mf_des_t4t_cc_file, TYPE_4_TAG_T4T_CC_EF_ID); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + FURI_LOG_D(TAG, "Select CC"); + error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Write DESFire CC"); + instance->data->t4t_version.value = TYPE_4_TAG_T4T_CC_VNO; + instance->data->chunk_max_read = 0x3A; + instance->data->chunk_max_write = 0x34; + instance->data->ndef_file_id = TYPE_4_TAG_T4T_NDEF_EF_ID; + instance->data->ndef_max_len = TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE; + instance->data->ndef_read_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + instance->data->ndef_write_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + instance->data->is_tag_specific = true; + uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE]; + type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf)); + error = type_4_tag_poller_iso_write(instance, 0, sizeof(cc_buf), cc_buf); + if(error != Type4TagErrorNone) break; + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Create DESFire NDEF"); + MfDesfireFileSettings mf_des_t4t_ndef_file = mf_des_t4t_ndef_file_default; + mf_des_t4t_ndef_file.data.size = sizeof(uint16_t) + (instance->data->is_tag_specific ? + instance->data->ndef_max_len : + TYPE_4_TAG_DEFAULT_NDEF_SIZE); + mf_des_error = mf_desfire_poller_create_file( + mf_des, MF_DES_T4T_NDEF_FILE_ID, &mf_des_t4t_ndef_file, TYPE_4_TAG_T4T_NDEF_EF_ID); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Write NDEF len"); + uint16_t ndef_len = simple_array_get_count(instance->data->ndef_data); + uint8_t ndef_len_be[sizeof(ndef_len)]; + bit_lib_num_to_bytes_be(ndef_len, sizeof(ndef_len_be), ndef_len_be); + error = type_4_tag_poller_iso_write(instance, 0, sizeof(ndef_len_be), ndef_len_be); + if(error != Type4TagErrorNone) break; + + if(ndef_len == 0) { + FURI_LOG_D(TAG, "NDEF file is empty"); + break; + } + + FURI_LOG_D(TAG, "Write NDEF"); + uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data); + error = type_4_tag_poller_iso_write(instance, sizeof(ndef_len), ndef_len, ndef_buf); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, "Wrote %hu bytes to NDEF file %04X", ndef_len, instance->data->ndef_file_id); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h new file mode 100644 index 000000000..1f9142e6e --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h @@ -0,0 +1,61 @@ +#pragma once + +#include "type_4_tag_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagPollerStateIdle, + Type4TagPollerStateRequestMode, + Type4TagPollerStateDetectPlatform, + Type4TagPollerStateSelectApplication, + Type4TagPollerStateReadCapabilityContainer, + Type4TagPollerStateReadNdefMessage, + Type4TagPollerStateCreateApplication, + Type4TagPollerStateCreateCapabilityContainer, + Type4TagPollerStateCreateNdefMessage, + Type4TagPollerStateWriteNdefMessage, + Type4TagPollerStateFailed, + Type4TagPollerStateSuccess, + + Type4TagPollerStateNum, +} Type4TagPollerState; + +struct Type4TagPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Type4TagPollerState state; + Type4TagPollerMode mode; + Type4TagError error; + Type4TagData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Type4TagPollerEventData type_4_tag_event_data; + Type4TagPollerEvent type_4_tag_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance); + +#ifdef __cplusplus +} +#endif