mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 12:42:30 +04:00
nfc lib
This commit is contained in:
@@ -19,39 +19,48 @@ env.Append(
|
|||||||
File("protocols/iso14443_3b/iso14443_3b.h"),
|
File("protocols/iso14443_3b/iso14443_3b.h"),
|
||||||
File("protocols/iso14443_4a/iso14443_4a.h"),
|
File("protocols/iso14443_4a/iso14443_4a.h"),
|
||||||
File("protocols/iso14443_4b/iso14443_4b.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_ultralight/mf_ultralight.h"),
|
||||||
File("protocols/mf_classic/mf_classic.h"),
|
File("protocols/mf_classic/mf_classic.h"),
|
||||||
File("protocols/mf_plus/mf_plus.h"),
|
File("protocols/mf_plus/mf_plus.h"),
|
||||||
File("protocols/mf_desfire/mf_desfire.h"),
|
File("protocols/mf_desfire/mf_desfire.h"),
|
||||||
File("protocols/emv/emv.h"),
|
|
||||||
File("protocols/slix/slix.h"),
|
File("protocols/slix/slix.h"),
|
||||||
File("protocols/st25tb/st25tb.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
|
# Pollers
|
||||||
File("protocols/iso14443_3a/iso14443_3a_poller.h"),
|
File("protocols/iso14443_3a/iso14443_3a_poller.h"),
|
||||||
File("protocols/iso14443_3b/iso14443_3b_poller.h"),
|
File("protocols/iso14443_3b/iso14443_3b_poller.h"),
|
||||||
File("protocols/iso14443_4a/iso14443_4a_poller.h"),
|
File("protocols/iso14443_4a/iso14443_4a_poller.h"),
|
||||||
File("protocols/iso14443_4b/iso14443_4b_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_ultralight/mf_ultralight_poller.h"),
|
||||||
File("protocols/mf_classic/mf_classic_poller.h"),
|
File("protocols/mf_classic/mf_classic_poller.h"),
|
||||||
File("protocols/mf_plus/mf_plus_poller.h"),
|
File("protocols/mf_plus/mf_plus_poller.h"),
|
||||||
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
||||||
File("protocols/slix/slix_poller.h"),
|
File("protocols/slix/slix_poller.h"),
|
||||||
File("protocols/emv/emv_poller.h"),
|
|
||||||
File("protocols/st25tb/st25tb_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
|
# Listeners
|
||||||
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
|
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
|
||||||
File("protocols/iso14443_4a/iso14443_4a_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_ultralight/mf_ultralight_listener.h"),
|
||||||
File("protocols/mf_classic/mf_classic_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
|
# Sync API
|
||||||
File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"),
|
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_ultralight/mf_ultralight_poller_sync.h"),
|
||||||
File("protocols/mf_classic/mf_classic_poller_sync.h"),
|
File("protocols/mf_classic/mf_classic_poller_sync.h"),
|
||||||
File("protocols/st25tb/st25tb_poller_sync.h"),
|
File("protocols/st25tb/st25tb_poller_sync.h"),
|
||||||
File("protocols/felica/felica_poller_sync.h"),
|
|
||||||
# Misc
|
# Misc
|
||||||
File("helpers/nfc_util.h"),
|
File("helpers/nfc_util.h"),
|
||||||
File("helpers/iso14443_crc.h"),
|
File("helpers/iso14443_crc.h"),
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#define ISO14443_4_BLOCK_PCB_MASK (0x03)
|
#define ISO14443_4_BLOCK_PCB_MASK (0x03)
|
||||||
|
|
||||||
#define ISO14443_4_BLOCK_PCB_I (0U)
|
#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_NAD_OFFSET (2)
|
||||||
#define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3)
|
#define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3)
|
||||||
#define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4)
|
#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_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_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_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) \
|
#define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \
|
||||||
ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK)
|
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) \
|
#define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \
|
||||||
ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK)
|
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 {
|
struct Iso14443_4Layer {
|
||||||
uint8_t pcb;
|
uint8_t pcb;
|
||||||
uint8_t pcb_prev;
|
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_prev = instance->pcb;
|
||||||
instance->pcb ^= (uint8_t)0x01;
|
if(toggle_num) {
|
||||||
|
instance->pcb ^= (uint8_t)0x01;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Iso14443_4Layer* iso14443_4_layer_alloc(void) {
|
Iso14443_4Layer* iso14443_4_layer_alloc(void) {
|
||||||
@@ -73,6 +90,9 @@ void iso14443_4_layer_reset(Iso14443_4Layer* instance) {
|
|||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
instance->pcb_prev = 0;
|
instance->pcb_prev = 0;
|
||||||
instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB;
|
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) {
|
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;
|
(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,
|
Iso14443_4Layer* instance,
|
||||||
const BitBuffer* input_data,
|
const BitBuffer* input_data,
|
||||||
BitBuffer* block_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_byte(block_data, instance->pcb);
|
||||||
bit_buffer_append(block_data, input_data);
|
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) {
|
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];
|
return data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool iso14443_4_layer_decode_block(
|
bool iso14443_4_layer_decode_response(
|
||||||
Iso14443_4Layer* instance,
|
Iso14443_4Layer* instance,
|
||||||
BitBuffer* output_data,
|
BitBuffer* output_data,
|
||||||
const BitBuffer* block_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)) &&
|
ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) &&
|
||||||
(!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb));
|
(!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb));
|
||||||
instance->pcb &= ISO14443_4_BLOCK_PCB_MASK;
|
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)) {
|
} 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);
|
const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data);
|
||||||
ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) &&
|
ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) &&
|
||||||
@@ -147,7 +167,7 @@ bool iso14443_4_layer_decode_block(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
|
Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext(
|
||||||
Iso14443_4Layer* instance,
|
Iso14443_4Layer* instance,
|
||||||
BitBuffer* output_data,
|
BitBuffer* output_data,
|
||||||
const BitBuffer* block_data) {
|
const BitBuffer* block_data) {
|
||||||
@@ -199,3 +219,100 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext(
|
|||||||
|
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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_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_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,
|
Iso14443_4Layer* instance,
|
||||||
const BitBuffer* input_data,
|
const BitBuffer* input_data,
|
||||||
BitBuffer* block_data);
|
BitBuffer* block_data);
|
||||||
|
|
||||||
bool iso14443_4_layer_decode_block(
|
bool iso14443_4_layer_decode_response(
|
||||||
Iso14443_4Layer* instance,
|
Iso14443_4Layer* instance,
|
||||||
BitBuffer* output_data,
|
BitBuffer* output_data,
|
||||||
const BitBuffer* block_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,
|
Iso14443_4Layer* instance,
|
||||||
BitBuffer* output_data,
|
BitBuffer* output_data,
|
||||||
const BitBuffer* block_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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
115
lib/nfc/helpers/nxp_native_command.c
Normal file
115
lib/nfc/helpers/nxp_native_command.c
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include "nxp_native_command.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
92
lib/nfc/helpers/nxp_native_command.h
Normal file
92
lib/nfc/helpers/nxp_native_command.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nxp_native_command_mode.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
|
||||||
|
|
||||||
|
// 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);
|
||||||
11
lib/nfc/helpers/nxp_native_command_mode.h
Normal file
11
lib/nfc/helpers/nxp_native_command_mode.h
Normal file
@@ -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;
|
||||||
@@ -646,33 +646,6 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
|||||||
return ret;
|
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(
|
NfcError nfc_felica_listener_set_sensf_res_data(
|
||||||
Nfc* instance,
|
Nfc* instance,
|
||||||
const uint8_t* idm,
|
const uint8_t* idm,
|
||||||
|
|||||||
@@ -380,30 +380,6 @@ NfcError nfc_felica_listener_set_sensf_res_data(
|
|||||||
*/
|
*/
|
||||||
NfcError nfc_iso15693_listener_tx_sof(Nfc* instance);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -26,6 +26,47 @@ typedef struct {
|
|||||||
Iso14443_3aListenerEventData* data;
|
Iso14443_3aListenerEventData* data;
|
||||||
} Iso14443_3aListenerEvent;
|
} 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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -26,17 +26,6 @@ struct Iso14443_3aListener {
|
|||||||
void* context;
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include "iso14443_4a.h"
|
#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
|
// ATS bit definitions
|
||||||
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
|
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ static Iso14443_4aListener*
|
|||||||
Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener));
|
Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener));
|
||||||
instance->iso14443_3a_listener = iso14443_3a_listener;
|
instance->iso14443_3a_listener = iso14443_3a_listener;
|
||||||
instance->data = data;
|
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->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE);
|
||||||
|
|
||||||
instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data;
|
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) {
|
static void iso14443_4a_listener_free(Iso14443_4aListener* instance) {
|
||||||
furi_assert(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);
|
bit_buffer_free(instance->tx_buffer);
|
||||||
free(instance);
|
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(
|
static void iso14443_4a_listener_set_callback(
|
||||||
Iso14443_4aListener* instance,
|
Iso14443_4aListener* instance,
|
||||||
NfcGenericCallback callback,
|
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) ==
|
if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) ==
|
||||||
Iso14443_4aErrorNone) {
|
Iso14443_4aErrorNone) {
|
||||||
instance->state = Iso14443_4aListenerStateActive;
|
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 {
|
} else {
|
||||||
instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData;
|
Iso14443_4LayerResult status = iso14443_4_layer_decode_command(
|
||||||
instance->iso14443_4a_event.data->buffer = rx_buffer;
|
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) {
|
if(instance->callback) {
|
||||||
command = instance->callback(instance->generic_event, instance->context);
|
command = instance->callback(instance->generic_event, instance->context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(
|
} else if(
|
||||||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ||
|
iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ||
|
||||||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) {
|
iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) {
|
||||||
instance->state = Iso14443_4aListenerStateIdle;
|
iso14443_4a_listener_reset(instance);
|
||||||
|
|
||||||
instance->iso14443_4a_event.type = iso14443_3a_event->type ==
|
instance->iso14443_4a_event.type = iso14443_3a_event->type ==
|
||||||
Iso14443_3aListenerEventTypeHalted ?
|
Iso14443_3aListenerEventTypeHalted ?
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ typedef struct {
|
|||||||
Iso14443_4aListenerEventData* data;
|
Iso14443_4aListenerEventData* data;
|
||||||
} Iso14443_4aListenerEvent;
|
} 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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -30,3 +30,17 @@ Iso14443_4aError
|
|||||||
instance->iso14443_3a_listener, instance->tx_buffer);
|
instance->iso14443_3a_listener, instance->tx_buffer);
|
||||||
return iso14443_4a_process_error(error);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <nfc/protocols/nfc_generic_event.h>
|
#include <nfc/protocols/nfc_generic_event.h>
|
||||||
|
#include <nfc/helpers/iso14443_4_layer.h>
|
||||||
|
|
||||||
#include "iso14443_4a_listener.h"
|
#include "iso14443_4a_listener.h"
|
||||||
#include "iso14443_4a_i.h"
|
#include "iso14443_4a_i.h"
|
||||||
@@ -17,8 +18,10 @@ typedef enum {
|
|||||||
struct Iso14443_4aListener {
|
struct Iso14443_4aListener {
|
||||||
Iso14443_3aListener* iso14443_3a_listener;
|
Iso14443_3aListener* iso14443_3a_listener;
|
||||||
Iso14443_4aData* data;
|
Iso14443_4aData* data;
|
||||||
|
Iso14443_4Layer* iso14443_4_layer;
|
||||||
Iso14443_4aListenerState state;
|
Iso14443_4aListenerState state;
|
||||||
|
|
||||||
|
BitBuffer* rx_buffer;
|
||||||
BitBuffer* tx_buffer;
|
BitBuffer* tx_buffer;
|
||||||
|
|
||||||
NfcGenericEvent generic_event;
|
NfcGenericEvent generic_event;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ Iso14443_4aError iso14443_4a_poller_send_block(
|
|||||||
furi_check(rx_buffer);
|
furi_check(rx_buffer);
|
||||||
|
|
||||||
bit_buffer_reset(instance->tx_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;
|
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));
|
} 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)) {
|
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
|
||||||
error = Iso14443_4aErrorProtocol;
|
error = Iso14443_4aErrorProtocol;
|
||||||
break;
|
break;
|
||||||
@@ -155,7 +155,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
|||||||
|
|
||||||
uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS;
|
uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS;
|
||||||
bit_buffer_reset(instance->tx_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;
|
Iso14443_4aError error = Iso14443_4aErrorNone;
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext(
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
} else {
|
} 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);
|
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer);
|
||||||
if(error == Iso14443_4aErrorSendExtra) {
|
if(error == Iso14443_4aErrorSendExtra) {
|
||||||
if(--attempts_left == 0) break;
|
if(--attempts_left == 0) break;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Iso14443_4bError iso14443_4b_poller_send_block(
|
|||||||
furi_check(rx_buffer);
|
furi_check(rx_buffer);
|
||||||
|
|
||||||
bit_buffer_reset(instance->tx_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;
|
Iso14443_4bError error = Iso14443_4bErrorNone;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ Iso14443_4bError iso14443_4b_poller_send_block(
|
|||||||
error = iso14443_4b_process_error(iso14443_3b_error);
|
error = iso14443_4b_process_error(iso14443_3b_error);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else if(!iso14443_4_layer_decode_block(
|
} else if(!iso14443_4_layer_decode_response(
|
||||||
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
|
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
|
||||||
error = Iso14443_4bErrorProtocol;
|
error = Iso14443_4bErrorProtocol;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ extern "C" {
|
|||||||
#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F)
|
#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F)
|
||||||
#define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5)
|
#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_READ_DATA (0xBD)
|
||||||
#define MF_DESFIRE_CMD_GET_VALUE (0x6C)
|
#define MF_DESFIRE_CMD_GET_VALUE (0x6C)
|
||||||
#define MF_DESFIRE_CMD_READ_RECORDS (0xBB)
|
#define MF_DESFIRE_CMD_READ_RECORDS (0xBB)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) {
|
|||||||
bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion));
|
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) {
|
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;
|
return can_parse;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) {
|
typedef struct FURI_PACKED {
|
||||||
typedef struct FURI_PACKED {
|
bool is_master_key_changeable : 1;
|
||||||
bool is_master_key_changeable : 1;
|
bool is_free_directory_list : 1;
|
||||||
bool is_free_directory_list : 1;
|
bool is_free_create_delete : 1;
|
||||||
bool is_free_create_delete : 1;
|
bool is_config_changeable : 1;
|
||||||
bool is_config_changeable : 1;
|
uint8_t change_key_id : 4;
|
||||||
uint8_t change_key_id : 4;
|
uint8_t max_keys : 4;
|
||||||
uint8_t max_keys : 4;
|
uint8_t flags : 4;
|
||||||
uint8_t flags : 4;
|
} MfDesfireKeySettingsLayout;
|
||||||
} MfDesfireKeySettingsLayout;
|
|
||||||
|
|
||||||
|
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) {
|
||||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout);
|
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout);
|
||||||
|
|
||||||
if(can_parse) {
|
if(can_parse) {
|
||||||
@@ -111,6 +111,21 @@ bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer*
|
|||||||
return can_parse;
|
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) {
|
bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) {
|
||||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion);
|
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion);
|
||||||
|
|
||||||
|
|||||||
@@ -2,55 +2,11 @@
|
|||||||
|
|
||||||
#include "mf_desfire.h"
|
#include "mf_desfire.h"
|
||||||
|
|
||||||
|
#include <nfc/helpers/nxp_native_command.h>
|
||||||
|
|
||||||
#define MF_DESFIRE_FFF_PICC_PREFIX "PICC"
|
#define MF_DESFIRE_FFF_PICC_PREFIX "PICC"
|
||||||
#define MF_DESFIRE_FFF_APP_PREFIX "Application"
|
#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
|
// SimpleArray configurations
|
||||||
|
|
||||||
extern const SimpleArrayConfig mf_desfire_key_version_array_config;
|
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);
|
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_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf);
|
||||||
|
|
||||||
bool mf_desfire_application_id_parse(
|
bool mf_desfire_application_id_parse(
|
||||||
|
|||||||
@@ -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);
|
MfDesfireError error = mf_desfire_poller_read_key_version(instance, 0, &key_version);
|
||||||
if(error != MfDesfireErrorNone) break;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
MfDesfireVersion version = {};
|
error = mf_desfire_poller_read_version(instance, &instance->data->version);
|
||||||
error = mf_desfire_poller_read_version(instance, &version);
|
|
||||||
if(error != MfDesfireErrorNone) break;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
protocol_detected = true;
|
protocol_detected = true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "mf_desfire.h"
|
#include "mf_desfire.h"
|
||||||
|
|
||||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
|
||||||
|
#include <lib/nfc/helpers/nxp_native_command_mode.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -38,6 +39,16 @@ typedef struct {
|
|||||||
MfDesfirePollerEventData* data; /**< Pointer to event specific data. */
|
MfDesfirePollerEventData* data; /**< Pointer to event specific data. */
|
||||||
} MfDesfirePollerEvent;
|
} 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.
|
* @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.
|
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
|
||||||
* @return MfDesfireErrorNone on success, an error code on failure.
|
* @return MfDesfireErrorNone on success, an error code on failure.
|
||||||
*/
|
*/
|
||||||
MfDesfireError mf_desfire_send_chunks(
|
MfDesfireError mf_desfire_poller_send_chunks(
|
||||||
MfDesfirePoller* instance,
|
MfDesfirePoller* instance,
|
||||||
const BitBuffer* tx_buffer,
|
const BitBuffer* tx_buffer,
|
||||||
BitBuffer* rx_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.
|
* @brief Read MfDesfire card version.
|
||||||
*
|
*
|
||||||
@@ -187,6 +203,44 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi(
|
|||||||
const SimpleArray* file_ids,
|
const SimpleArray* file_ids,
|
||||||
SimpleArray* data);
|
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.
|
* @brief Read file data on MfDesfire card.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "mf_desfire_poller_i.h"
|
#include "mf_desfire_poller_i.h"
|
||||||
|
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
|
||||||
#include "mf_desfire_i.h"
|
#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) {
|
MfDesfireError mf_desfire_process_status_code(uint8_t status_code) {
|
||||||
switch(status_code) {
|
switch(status_code) {
|
||||||
case MF_DESFIRE_STATUS_OPERATION_OK:
|
case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK:
|
||||||
return MfDesfireErrorNone;
|
return MfDesfireErrorNone;
|
||||||
case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR:
|
case NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR:
|
||||||
return MfDesfireErrorAuthentication;
|
return MfDesfireErrorAuthentication;
|
||||||
case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE:
|
case NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE:
|
||||||
return MfDesfireErrorCommandNotSupported;
|
return MfDesfireErrorCommandNotSupported;
|
||||||
default:
|
default:
|
||||||
return MfDesfireErrorProtocol;
|
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,
|
MfDesfirePoller* instance,
|
||||||
const BitBuffer* tx_buffer,
|
const BitBuffer* tx_buffer,
|
||||||
BitBuffer* rx_buffer) {
|
BitBuffer* rx_buffer) {
|
||||||
furi_check(instance);
|
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 {
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
|
return mf_desfire_process_error(iso14443_4a_error);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return error;
|
return mf_desfire_process_status_code(status_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) {
|
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;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
@@ -124,7 +98,8 @@ MfDesfireError
|
|||||||
MfDesfireError error;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
@@ -146,7 +121,8 @@ MfDesfireError
|
|||||||
MfDesfireError error;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
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);
|
bit_buffer_set_byte(instance->input_buffer, 1, key_num);
|
||||||
|
|
||||||
MfDesfireError error =
|
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(error == MfDesfireErrorNone) {
|
||||||
if(!mf_desfire_key_version_parse(data, instance->result_buffer)) {
|
if(!mf_desfire_key_version_parse(data, instance->result_buffer)) {
|
||||||
error = MfDesfireErrorProtocol;
|
error = MfDesfireErrorProtocol;
|
||||||
@@ -210,7 +186,8 @@ MfDesfireError
|
|||||||
MfDesfireError error;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
@@ -243,7 +220,7 @@ MfDesfireError mf_desfire_poller_select_application(
|
|||||||
instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId));
|
instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId));
|
||||||
|
|
||||||
MfDesfireError error =
|
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;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -258,7 +235,8 @@ MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, Simple
|
|||||||
MfDesfireError error;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
@@ -293,7 +271,8 @@ MfDesfireError mf_desfire_poller_read_file_settings(
|
|||||||
MfDesfireError error;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
@@ -329,6 +308,108 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi(
|
|||||||
return error;
|
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(
|
static MfDesfireError mf_desfire_poller_read_file(
|
||||||
MfDesfirePoller* instance,
|
MfDesfirePoller* instance,
|
||||||
MfDesfireFileId id,
|
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*)¤t_offset, 3);
|
||||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer);
|
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;
|
MfDesfireError error;
|
||||||
|
|
||||||
do {
|
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;
|
if(error != MfDesfireErrorNone) break;
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ typedef enum {
|
|||||||
|
|
||||||
struct MfDesfirePoller {
|
struct MfDesfirePoller {
|
||||||
Iso14443_4aPoller* iso14443_4a_poller;
|
Iso14443_4aPoller* iso14443_4a_poller;
|
||||||
|
NxpNativeCommandMode command_mode;
|
||||||
MfDesfirePollerSessionState session_state;
|
MfDesfirePollerSessionState session_state;
|
||||||
MfDesfirePollerState state;
|
MfDesfirePollerState state;
|
||||||
MfDesfireError error;
|
MfDesfireError error;
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
const uint8_t mf_plus_ats_t1_tk_values[][MF_PLUS_T1_TK_VALUE_LEN] = {
|
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, 0x00, 0x35, 0xC7}, // Mifare Plus S
|
||||||
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X
|
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X
|
||||||
{0xC1, 0x05, 0x2F, 0x2F, 0x00, 0xF6, 0xD1}, // Mifare Plus SE
|
{0xC1, 0x05, 0x21, 0x30, 0x00, 0xF6, 0xD1}, // Mifare Plus SE
|
||||||
{0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE
|
{0xC1, 0x05, 0x21, 0x30, 0x10, 0xF6, 0xD1}, // Mifare Plus SE
|
||||||
};
|
};
|
||||||
|
|
||||||
MfPlusError mf_plus_get_type_from_version(
|
MfPlusError mf_plus_get_type_from_version(
|
||||||
@@ -27,7 +27,7 @@ MfPlusError mf_plus_get_type_from_version(
|
|||||||
|
|
||||||
MfPlusError error = MfPlusErrorProtocol;
|
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;
|
error = MfPlusErrorNone;
|
||||||
// Mifare Plus EV1/EV2
|
// Mifare Plus EV1/EV2
|
||||||
|
|
||||||
@@ -85,16 +85,15 @@ MfPlusError
|
|||||||
|
|
||||||
MfPlusError error = MfPlusErrorProtocol;
|
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;
|
return MfPlusErrorProtocol;
|
||||||
}
|
}
|
||||||
|
const uint8_t* historical_bytes = simple_array_cget_data(iso4_data->ats_data.t1_tk);
|
||||||
|
|
||||||
switch(iso4_data->iso14443_3a_data->sak) {
|
switch(iso4_data->iso14443_3a_data->sak) {
|
||||||
case 0x08:
|
case 0x08:
|
||||||
if(memcmp(
|
if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) {
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[0],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
// Mifare Plus S 2K SL1
|
// Mifare Plus S 2K SL1
|
||||||
mf_plus_data->type = MfPlusTypeS;
|
mf_plus_data->type = MfPlusTypeS;
|
||||||
mf_plus_data->size = MfPlusSize2K;
|
mf_plus_data->size = MfPlusSize2K;
|
||||||
@@ -102,11 +101,7 @@ MfPlusError
|
|||||||
|
|
||||||
FURI_LOG_D(TAG, "Mifare Plus S 2K SL1");
|
FURI_LOG_D(TAG, "Mifare Plus S 2K SL1");
|
||||||
error = MfPlusErrorNone;
|
error = MfPlusErrorNone;
|
||||||
} else if(
|
} else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) {
|
||||||
memcmp(
|
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[1],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
// Mifare Plus X 2K SL1
|
// Mifare Plus X 2K SL1
|
||||||
mf_plus_data->type = MfPlusTypeX;
|
mf_plus_data->type = MfPlusTypeX;
|
||||||
mf_plus_data->size = MfPlusSize2K;
|
mf_plus_data->size = MfPlusSize2K;
|
||||||
@@ -115,14 +110,8 @@ MfPlusError
|
|||||||
FURI_LOG_D(TAG, "Mifare Plus X 2K SL1");
|
FURI_LOG_D(TAG, "Mifare Plus X 2K SL1");
|
||||||
error = MfPlusErrorNone;
|
error = MfPlusErrorNone;
|
||||||
} else if(
|
} else if(
|
||||||
memcmp(
|
memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 ||
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) {
|
||||||
mf_plus_ats_t1_tk_values[2],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 ||
|
|
||||||
memcmp(
|
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[3],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
// Mifare Plus SE 1K SL1
|
// Mifare Plus SE 1K SL1
|
||||||
mf_plus_data->type = MfPlusTypeSE;
|
mf_plus_data->type = MfPlusTypeSE;
|
||||||
mf_plus_data->size = MfPlusSize1K;
|
mf_plus_data->size = MfPlusSize1K;
|
||||||
@@ -154,10 +143,7 @@ MfPlusError
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 0x18:
|
case 0x18:
|
||||||
if(memcmp(
|
if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) {
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[0],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
// Mifare Plus S 4K SL1
|
// Mifare Plus S 4K SL1
|
||||||
mf_plus_data->type = MfPlusTypeS;
|
mf_plus_data->type = MfPlusTypeS;
|
||||||
mf_plus_data->size = MfPlusSize4K;
|
mf_plus_data->size = MfPlusSize4K;
|
||||||
@@ -165,11 +151,7 @@ MfPlusError
|
|||||||
|
|
||||||
FURI_LOG_D(TAG, "Mifare Plus S 4K SL1");
|
FURI_LOG_D(TAG, "Mifare Plus S 4K SL1");
|
||||||
error = MfPlusErrorNone;
|
error = MfPlusErrorNone;
|
||||||
} else if(
|
} else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) {
|
||||||
memcmp(
|
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[1],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
// Mifare Plus X 4K SL1
|
// Mifare Plus X 4K SL1
|
||||||
mf_plus_data->type = MfPlusTypeX;
|
mf_plus_data->type = MfPlusTypeX;
|
||||||
mf_plus_data->size = MfPlusSize4K;
|
mf_plus_data->size = MfPlusSize4K;
|
||||||
@@ -183,10 +165,7 @@ MfPlusError
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 0x20:
|
case 0x20:
|
||||||
if(memcmp(
|
if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) {
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[0],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
// Mifare Plus S 2/4K SL3
|
// Mifare Plus S 2/4K SL3
|
||||||
FURI_LOG_D(TAG, "Mifare Plus S SL3");
|
FURI_LOG_D(TAG, "Mifare Plus S SL3");
|
||||||
mf_plus_data->type = MfPlusTypeS;
|
mf_plus_data->type = MfPlusTypeS;
|
||||||
@@ -207,21 +186,20 @@ MfPlusError
|
|||||||
} else {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)");
|
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)");
|
||||||
}
|
}
|
||||||
} else if(
|
} else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) {
|
||||||
memcmp(
|
// Mifare Plus X 2/4K SL3
|
||||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
|
||||||
mf_plus_ats_t1_tk_values[1],
|
|
||||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
|
||||||
mf_plus_data->type = MfPlusTypeX;
|
mf_plus_data->type = MfPlusTypeX;
|
||||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||||
FURI_LOG_D(TAG, "Mifare Plus X SL3");
|
FURI_LOG_D(TAG, "Mifare Plus X SL3");
|
||||||
|
|
||||||
if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) {
|
if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) {
|
||||||
|
// Mifare Plus X 2K SL3
|
||||||
mf_plus_data->size = MfPlusSize2K;
|
mf_plus_data->size = MfPlusSize2K;
|
||||||
|
|
||||||
FURI_LOG_D(TAG, "Mifare Plus X 2K SL3");
|
FURI_LOG_D(TAG, "Mifare Plus X 2K SL3");
|
||||||
error = MfPlusErrorNone;
|
error = MfPlusErrorNone;
|
||||||
} else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) {
|
} else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) {
|
||||||
|
// Mifare Plus X 4K SL3
|
||||||
mf_plus_data->size = MfPlusSize4K;
|
mf_plus_data->size = MfPlusSize4K;
|
||||||
|
|
||||||
FURI_LOG_D(TAG, "Mifare Plus X 4K SL3");
|
FURI_LOG_D(TAG, "Mifare Plus X 4K SL3");
|
||||||
@@ -229,6 +207,16 @@ MfPlusError
|
|||||||
} else {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)");
|
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 {
|
} else {
|
||||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type");
|
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) {
|
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) {
|
if(can_parse) {
|
||||||
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
|
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
|
||||||
} else if(
|
} else {
|
||||||
bit_buffer_get_size_bytes(buf) == 8 &&
|
memset(data, 0, sizeof(MfPlusVersion));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol;
|
return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol;
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
#include "mf_plus.h"
|
#include "mf_plus.h"
|
||||||
|
|
||||||
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
|
#include <nfc/helpers/nxp_native_command.h>
|
||||||
|
|
||||||
#define MF_PLUS_STATUS_OPERATION_OK (0x90)
|
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
|
||||||
#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF)
|
|
||||||
|
|
||||||
MfPlusError mf_plus_get_type_from_version(
|
MfPlusError mf_plus_get_type_from_version(
|
||||||
const Iso14443_4aData* iso14443_4a_data,
|
const Iso14443_4aData* iso14443_4a_data,
|
||||||
|
|||||||
@@ -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,
|
MfPlusPoller* instance,
|
||||||
const BitBuffer* tx_buffer,
|
const BitBuffer* tx_buffer,
|
||||||
BitBuffer* rx_buffer) {
|
BitBuffer* rx_buffer) {
|
||||||
furi_assert(instance);
|
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(
|
NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK;
|
||||||
instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer);
|
Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller(
|
||||||
MfPlusError error = mf_plus_process_error(iso14443_4a_error);
|
instance->iso14443_4a_poller,
|
||||||
|
&status_code,
|
||||||
|
tx_buffer,
|
||||||
|
rx_buffer,
|
||||||
|
NxpNativeCommandModePlain,
|
||||||
|
instance->tx_buffer,
|
||||||
|
instance->rx_buffer);
|
||||||
|
|
||||||
if(error == MfPlusErrorNone) {
|
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||||
bit_buffer_copy(rx_buffer, instance->rx_buffer);
|
return mf_plus_process_error(iso14443_4a_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
bit_buffer_reset(instance->tx_buffer);
|
return mf_plus_process_status_code(status_code);
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) {
|
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);
|
bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION);
|
||||||
|
|
||||||
MfPlusError error =
|
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) {
|
if(error == MfPlusErrorNone) {
|
||||||
error = mf_plus_version_parse(data, instance->result_buffer);
|
error = mf_plus_version_parse(data, instance->result_buffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,11 @@
|
|||||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||||
#include <nfc/protocols/mf_plus/mf_plus.h>
|
#include <nfc/protocols/mf_plus/mf_plus.h>
|
||||||
#include <nfc/protocols/mf_desfire/mf_desfire.h>
|
#include <nfc/protocols/mf_desfire/mf_desfire.h>
|
||||||
#include <nfc/protocols/emv/emv.h>
|
|
||||||
#include <nfc/protocols/slix/slix_device_defs.h>
|
#include <nfc/protocols/slix/slix_device_defs.h>
|
||||||
#include <nfc/protocols/st25tb/st25tb.h>
|
#include <nfc/protocols/st25tb/st25tb.h>
|
||||||
|
#include <nfc/protocols/ntag4xx/ntag4xx.h>
|
||||||
|
#include <nfc/protocols/type_4_tag/type_4_tag.h>
|
||||||
|
#include <nfc/protocols/emv/emv.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief List of registered NFC device implementations.
|
* @brief List of registered NFC device implementations.
|
||||||
@@ -45,6 +47,8 @@ const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = {
|
|||||||
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
|
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
|
||||||
[NfcProtocolSlix] = &nfc_device_slix,
|
[NfcProtocolSlix] = &nfc_device_slix,
|
||||||
[NfcProtocolSt25tb] = &nfc_device_st25tb,
|
[NfcProtocolSt25tb] = &nfc_device_st25tb,
|
||||||
|
[NfcProtocolNtag4xx] = &nfc_device_ntag4xx,
|
||||||
|
[NfcProtocolType4Tag] = &nfc_device_type_4_tag,
|
||||||
[NfcProtocolEmv] = &nfc_device_emv,
|
[NfcProtocolEmv] = &nfc_device_emv,
|
||||||
/* Add new protocols here */
|
/* Add new protocols here */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
#include <nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h>
|
#include <nfc/protocols/iso14443_3a/iso14443_3a_listener_defs.h>
|
||||||
#include <nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h>
|
#include <nfc/protocols/iso14443_4a/iso14443_4a_listener_defs.h>
|
||||||
#include <nfc/protocols/iso15693_3/iso15693_3_listener_defs.h>
|
#include <nfc/protocols/iso15693_3/iso15693_3_listener_defs.h>
|
||||||
|
#include <nfc/protocols/felica/felica_listener_defs.h>
|
||||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h>
|
#include <nfc/protocols/mf_ultralight/mf_ultralight_listener_defs.h>
|
||||||
#include <nfc/protocols/mf_classic/mf_classic_listener_defs.h>
|
#include <nfc/protocols/mf_classic/mf_classic_listener_defs.h>
|
||||||
#include <nfc/protocols/slix/slix_listener_defs.h>
|
#include <nfc/protocols/slix/slix_listener_defs.h>
|
||||||
#include <nfc/protocols/felica/felica_listener_defs.h>
|
#include <nfc/protocols/type_4_tag/type_4_tag_listener_defs.h>
|
||||||
|
|
||||||
const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = {
|
const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = {
|
||||||
[NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a,
|
[NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a,
|
||||||
@@ -14,11 +15,14 @@ const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = {
|
|||||||
[NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a,
|
[NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a,
|
||||||
[NfcProtocolIso14443_4b] = NULL,
|
[NfcProtocolIso14443_4b] = NULL,
|
||||||
[NfcProtocolIso15693_3] = &nfc_listener_iso15693_3,
|
[NfcProtocolIso15693_3] = &nfc_listener_iso15693_3,
|
||||||
|
[NfcProtocolFelica] = &nfc_listener_felica,
|
||||||
[NfcProtocolMfUltralight] = &mf_ultralight_listener,
|
[NfcProtocolMfUltralight] = &mf_ultralight_listener,
|
||||||
[NfcProtocolMfClassic] = &mf_classic_listener,
|
[NfcProtocolMfClassic] = &mf_classic_listener,
|
||||||
|
[NfcProtocolMfPlus] = NULL,
|
||||||
[NfcProtocolMfDesfire] = NULL,
|
[NfcProtocolMfDesfire] = NULL,
|
||||||
[NfcProtocolSlix] = &nfc_listener_slix,
|
[NfcProtocolSlix] = &nfc_listener_slix,
|
||||||
[NfcProtocolSt25tb] = NULL,
|
[NfcProtocolSt25tb] = NULL,
|
||||||
[NfcProtocolFelica] = &nfc_listener_felica,
|
[NfcProtocolNtag4xx] = NULL,
|
||||||
|
[NfcProtocolType4Tag] = &nfc_listener_type_4_tag,
|
||||||
[NfcProtocolEmv] = NULL,
|
[NfcProtocolEmv] = NULL,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,9 +10,11 @@
|
|||||||
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
|
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
|
||||||
#include <nfc/protocols/mf_plus/mf_plus_poller_defs.h>
|
#include <nfc/protocols/mf_plus/mf_plus_poller_defs.h>
|
||||||
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
||||||
#include <nfc/protocols/emv/emv_poller_defs.h>
|
|
||||||
#include <nfc/protocols/slix/slix_poller_defs.h>
|
#include <nfc/protocols/slix/slix_poller_defs.h>
|
||||||
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
|
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
|
||||||
|
#include <nfc/protocols/ntag4xx/ntag4xx_poller_defs.h>
|
||||||
|
#include <nfc/protocols/type_4_tag/type_4_tag_poller_defs.h>
|
||||||
|
#include <nfc/protocols/emv/emv_poller_defs.h>
|
||||||
|
|
||||||
const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = {
|
const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = {
|
||||||
[NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a,
|
[NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a,
|
||||||
@@ -27,6 +29,8 @@ const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = {
|
|||||||
[NfcProtocolMfDesfire] = &mf_desfire_poller,
|
[NfcProtocolMfDesfire] = &mf_desfire_poller,
|
||||||
[NfcProtocolSlix] = &nfc_poller_slix,
|
[NfcProtocolSlix] = &nfc_poller_slix,
|
||||||
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
|
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
|
||||||
|
[NfcProtocolNtag4xx] = &ntag4xx_poller,
|
||||||
|
[NfcProtocolType4Tag] = &type_4_tag_poller,
|
||||||
[NfcProtocolEmv] = &emv_poller,
|
[NfcProtocolEmv] = &emv_poller,
|
||||||
/* Add new pollers here */
|
/* Add new pollers here */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
* ```
|
* ```
|
||||||
* **************************** Protocol tree structure ***************************
|
* **************************** Protocol tree structure ***************************
|
||||||
*
|
*
|
||||||
* (Start)
|
* (Start)
|
||||||
* |
|
* |
|
||||||
* +------------------------+-----------+---------+------------+
|
* +------------------------+-----------+---------+------------+
|
||||||
* | | | | |
|
* | | | | |
|
||||||
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
|
* ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB
|
||||||
* | | |
|
* | | |
|
||||||
* +---------------+-------------+ ISO14443-4B SLIX
|
* +---------------+-------------+ ISO14443-4B SLIX
|
||||||
* | | |
|
* | | |
|
||||||
* ISO14443-4A Mf Ultralight Mf Classic
|
* ISO14443-4A Mf Ultralight Mf Classic
|
||||||
* |
|
* |
|
||||||
* +-----+-----+
|
* +-----+----+----------+----------+---------+
|
||||||
* | |
|
* | | | | |
|
||||||
* Mf Desfire EMV
|
* Mf Desfire Mf Plus NTAG4xx Type 4 Tag EMV
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* When implementing a new protocol, its place in the tree must be determined first.
|
* 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. */
|
/** List of ISO14443-4A child protocols. */
|
||||||
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
|
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
|
||||||
NfcProtocolMfDesfire,
|
|
||||||
NfcProtocolMfPlus,
|
NfcProtocolMfPlus,
|
||||||
|
NfcProtocolMfDesfire,
|
||||||
|
NfcProtocolNtag4xx,
|
||||||
|
NfcProtocolType4Tag,
|
||||||
NfcProtocolEmv,
|
NfcProtocolEmv,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,6 +158,18 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = {
|
|||||||
.children_num = 0,
|
.children_num = 0,
|
||||||
.children_protocol = NULL,
|
.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] =
|
[NfcProtocolEmv] =
|
||||||
{
|
{
|
||||||
.parent_protocol = NfcProtocolIso14443_4a,
|
.parent_protocol = NfcProtocolIso14443_4a,
|
||||||
|
|||||||
@@ -188,6 +188,8 @@ typedef enum {
|
|||||||
NfcProtocolMfDesfire,
|
NfcProtocolMfDesfire,
|
||||||
NfcProtocolSlix,
|
NfcProtocolSlix,
|
||||||
NfcProtocolSt25tb,
|
NfcProtocolSt25tb,
|
||||||
|
NfcProtocolNtag4xx,
|
||||||
|
NfcProtocolType4Tag,
|
||||||
NfcProtocolEmv,
|
NfcProtocolEmv,
|
||||||
/* Add new protocols here */
|
/* Add new protocols here */
|
||||||
|
|
||||||
|
|||||||
192
lib/nfc/protocols/ntag4xx/ntag4xx.c
Normal file
192
lib/nfc/protocols/ntag4xx/ntag4xx.c
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#include "ntag4xx_i.h"
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
114
lib/nfc/protocols/ntag4xx/ntag4xx.h
Normal file
114
lib/nfc/protocols/ntag4xx/ntag4xx.h
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||||
|
|
||||||
|
#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
|
||||||
54
lib/nfc/protocols/ntag4xx/ntag4xx_i.c
Normal file
54
lib/nfc/protocols/ntag4xx/ntag4xx_i.c
Normal file
@@ -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));
|
||||||
|
}
|
||||||
25
lib/nfc/protocols/ntag4xx/ntag4xx_i.h
Normal file
25
lib/nfc/protocols/ntag4xx/ntag4xx_i.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ntag4xx.h"
|
||||||
|
|
||||||
|
#include <nfc/helpers/nxp_native_command.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
165
lib/nfc/protocols/ntag4xx/ntag4xx_poller.c
Normal file
165
lib/nfc/protocols/ntag4xx/ntag4xx_poller.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include "ntag4xx_poller_i.h"
|
||||||
|
|
||||||
|
#include <nfc/protocols/nfc_poller_base.h>
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#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,
|
||||||
|
};
|
||||||
43
lib/nfc/protocols/ntag4xx/ntag4xx_poller.h
Normal file
43
lib/nfc/protocols/ntag4xx/ntag4xx_poller.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ntag4xx.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
|
||||||
|
|
||||||
|
#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
|
||||||
5
lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h
Normal file
5
lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nfc/protocols/nfc_poller_base.h>
|
||||||
|
|
||||||
|
extern const NfcPollerBase ntag4xx_poller;
|
||||||
52
lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c
Normal file
52
lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include "ntag4xx_poller_i.h"
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
40
lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h
Normal file
40
lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ntag4xx_poller.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||||
|
|
||||||
|
#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
|
||||||
170
lib/nfc/protocols/type_4_tag/type_4_tag.c
Normal file
170
lib/nfc/protocols/type_4_tag/type_4_tag.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
84
lib/nfc/protocols/type_4_tag/type_4_tag.h
Normal file
84
lib/nfc/protocols/type_4_tag/type_4_tag.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||||
|
|
||||||
|
#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
|
||||||
163
lib/nfc/protocols/type_4_tag/type_4_tag_i.c
Normal file
163
lib/nfc/protocols/type_4_tag/type_4_tag_i.c
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include "type_4_tag_i.h"
|
||||||
|
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
110
lib/nfc/protocols/type_4_tag/type_4_tag_i.h
Normal file
110
lib/nfc/protocols/type_4_tag/type_4_tag_i.h
Normal file
@@ -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);
|
||||||
88
lib/nfc/protocols/type_4_tag/type_4_tag_listener.c
Normal file
88
lib/nfc/protocols/type_4_tag/type_4_tag_listener.c
Normal file
@@ -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,
|
||||||
|
};
|
||||||
26
lib/nfc/protocols/type_4_tag/type_4_tag_listener.h
Normal file
26
lib/nfc/protocols/type_4_tag/type_4_tag_listener.h
Normal file
@@ -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
|
||||||
5
lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h
Normal file
5
lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nfc/protocols/nfc_listener_base.h>
|
||||||
|
|
||||||
|
extern const NfcListenerBase nfc_listener_type_4_tag;
|
||||||
382
lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c
Normal file
382
lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
#include "type_4_tag_listener_i.h"
|
||||||
|
#include "type_4_tag_i.h"
|
||||||
|
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
|
||||||
|
#define TAG "Type4TagListener"
|
||||||
|
|
||||||
|
typedef Type4TagError (*Type4TagListenerApduHandler)(
|
||||||
|
Type4TagListener* instance,
|
||||||
|
uint8_t p1,
|
||||||
|
uint8_t p2,
|
||||||
|
size_t lc,
|
||||||
|
const uint8_t* data,
|
||||||
|
size_t le);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t cla_ins[2];
|
||||||
|
Type4TagListenerApduHandler handler;
|
||||||
|
} Type4TagListenerApduCommand;
|
||||||
|
|
||||||
|
static const uint8_t type_4_tag_success_apdu[] = {TYPE_4_TAG_ISO_STATUS_SUCCESS};
|
||||||
|
static const uint8_t type_4_tag_offset_error_apdu[] = {TYPE_4_TAG_ISO_STATUS_OFFSET_ERR};
|
||||||
|
static const uint8_t type_4_tag_not_found_apdu[] = {TYPE_4_TAG_ISO_STATUS_NOT_FOUND};
|
||||||
|
static const uint8_t type_4_tag_no_support_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_SUPPORT};
|
||||||
|
static const uint8_t type_4_tag_bad_params_apdu[] = {TYPE_4_TAG_ISO_STATUS_BAD_PARAMS};
|
||||||
|
static const uint8_t type_4_tag_no_cmd_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_CMD};
|
||||||
|
|
||||||
|
static Type4TagError type_4_tag_listener_iso_select(
|
||||||
|
Type4TagListener* instance,
|
||||||
|
uint8_t p1,
|
||||||
|
uint8_t p2,
|
||||||
|
size_t lc,
|
||||||
|
const uint8_t* data,
|
||||||
|
size_t le) {
|
||||||
|
UNUSED(p2);
|
||||||
|
UNUSED(le);
|
||||||
|
|
||||||
|
if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_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;
|
||||||
|
}
|
||||||
38
lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h
Normal file
38
lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "type_4_tag_listener.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
Type4TagListenerStateIdle,
|
||||||
|
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
|
||||||
289
lib/nfc/protocols/type_4_tag/type_4_tag_poller.c
Normal file
289
lib/nfc/protocols/type_4_tag/type_4_tag_poller.c
Normal file
@@ -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,
|
||||||
|
};
|
||||||
63
lib/nfc/protocols/type_4_tag/type_4_tag_poller.h
Normal file
63
lib/nfc/protocols/type_4_tag/type_4_tag_poller.h
Normal file
@@ -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
|
||||||
5
lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h
Normal file
5
lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nfc/protocols/nfc_poller_base.h>
|
||||||
|
|
||||||
|
extern const NfcPollerBase type_4_tag_poller;
|
||||||
501
lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c
Normal file
501
lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
#include "type_4_tag_poller_i.h"
|
||||||
|
#include "type_4_tag_i.h"
|
||||||
|
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
|
||||||
|
#include <nfc/nfc_device.h>
|
||||||
|
#include <nfc/protocols/ntag4xx/ntag4xx_poller.h>
|
||||||
|
#include <nfc/protocols/ntag4xx/ntag4xx_poller_defs.h>
|
||||||
|
#include <nfc/protocols/mf_desfire/mf_desfire_poller.h>
|
||||||
|
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
61
lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h
Normal file
61
lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "type_4_tag_poller.h"
|
||||||
|
|
||||||
|
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||||
|
|
||||||
|
#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
|
||||||
Reference in New Issue
Block a user