NFC refactoring (#3050)

"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring.

Starring:

- @gornekich - NFC refactoring project lead, architect, senior developer
- @gsurkov - architect, senior developer
- @RebornedBrain - senior developer

Supporting roles:

- @skotopes, @DrZlo13, @hedger - general architecture advisors, code review
- @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance

Special thanks:

@bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
This commit is contained in:
gornekich
2023-10-24 12:08:09 +09:00
committed by GitHub
parent 35c903494c
commit d92b0a82cc
514 changed files with 41487 additions and 68124 deletions
@@ -0,0 +1,627 @@
#include "mf_ultralight.h"
#include <nfc/helpers/nfc_util.h>
#include <furi.h>
#define MF_ULTRALIGHT_PROTOCOL_NAME "NTAG/Ultralight"
#define MF_ULTRALIGHT_FORMAT_VERSION_KEY "Data format version"
#define MF_ULTRALIGHT_TYPE_KEY MF_ULTRALIGHT_PROTOCOL_NAME " type"
#define MF_ULTRALIGHT_SIGNATURE_KEY "Signature"
#define MF_ULTRALIGHT_MIFARE_VERSION_KEY "Mifare version"
#define MF_ULTRALIGHT_COUNTER_KEY "Counter"
#define MF_ULTRALIGHT_TEARING_KEY "Tearing"
#define MF_ULTRALIGHT_PAGES_TOTAL_KEY "Pages total"
#define MF_ULTRALIGHT_PAGES_READ_KEY "Pages read"
#define MF_ULTRALIGHT_PAGE_KEY "Page"
#define MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY "Failed authentication attempts"
typedef struct {
const char* device_name;
uint16_t total_pages;
uint16_t config_page;
uint32_t feature_set;
} MfUltralightFeatures;
static const uint32_t mf_ultralight_data_format_version = 2;
static const MfUltralightFeatures mf_ultralight_features[MfUltralightTypeNum] = {
[MfUltralightTypeUnknown] =
{
.device_name = "Mifare Ultralight",
.total_pages = 16,
.config_page = 0,
.feature_set = MfUltralightFeatureSupportCompatibleWrite,
},
[MfUltralightTypeMfulC] =
{
.device_name = "Mifare Ultralight C",
.total_pages = 48,
.config_page = 0,
.feature_set = MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportAuthenticate,
},
[MfUltralightTypeNTAG203] =
{
.device_name = "NTAG203",
.total_pages = 42,
.config_page = 0,
.feature_set = MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportCounterInMemory,
},
[MfUltralightTypeUL11] =
{
.device_name = "Mifare Ultralight 11",
.total_pages = 20,
.config_page = 16,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportReadCounter |
MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl,
},
[MfUltralightTypeUL21] =
{
.device_name = "Mifare Ultralight 21",
.total_pages = 41,
.config_page = 37,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportReadCounter |
MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl |
MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAG213] =
{
.device_name = "NTAG213",
.total_pages = 45,
.config_page = 41,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter |
MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAG215] =
{
.device_name = "NTAG215",
.total_pages = 135,
.config_page = 131,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter |
MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAG216] =
{
.device_name = "NTAG216",
.total_pages = 231,
.config_page = 227,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportCompatibleWrite |
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter |
MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAGI2C1K] =
{
.device_name = "NTAG I2C 1K",
.total_pages = 231,
.config_page = 0,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAGI2C2K] =
{
.device_name = "NTAG I2C 2K",
.total_pages = 485,
.config_page = 0,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead |
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAGI2CPlus1K] =
{
.device_name = "NTAG I2C Plus 1K",
.total_pages = 236,
.config_page = 227,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth |
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite |
MfUltralightFeatureSupportDynamicLock,
},
[MfUltralightTypeNTAGI2CPlus2K] =
{
.device_name = "NTAG I2C Plus 2K",
.total_pages = 492,
.config_page = 227,
.feature_set =
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth |
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite |
MfUltralightFeatureSupportDynamicLock,
},
};
const NfcDeviceBase nfc_device_mf_ultralight = {
.protocol_name = MF_ULTRALIGHT_PROTOCOL_NAME,
.alloc = (NfcDeviceAlloc)mf_ultralight_alloc,
.free = (NfcDeviceFree)mf_ultralight_free,
.reset = (NfcDeviceReset)mf_ultralight_reset,
.copy = (NfcDeviceCopy)mf_ultralight_copy,
.verify = (NfcDeviceVerify)mf_ultralight_verify,
.load = (NfcDeviceLoad)mf_ultralight_load,
.save = (NfcDeviceSave)mf_ultralight_save,
.is_equal = (NfcDeviceEqual)mf_ultralight_is_equal,
.get_name = (NfcDeviceGetName)mf_ultralight_get_device_name,
.get_uid = (NfcDeviceGetUid)mf_ultralight_get_uid,
.set_uid = (NfcDeviceSetUid)mf_ultralight_set_uid,
.get_base_data = (NfcDeviceGetBaseData)mf_ultralight_get_base_data,
};
MfUltralightData* mf_ultralight_alloc() {
MfUltralightData* data = malloc(sizeof(MfUltralightData));
data->iso14443_3a_data = iso14443_3a_alloc();
return data;
}
void mf_ultralight_free(MfUltralightData* data) {
furi_assert(data);
iso14443_3a_free(data->iso14443_3a_data);
free(data);
}
void mf_ultralight_reset(MfUltralightData* data) {
furi_assert(data);
iso14443_3a_reset(data->iso14443_3a_data);
}
void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other) {
furi_assert(data);
furi_assert(other);
iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data);
for(size_t i = 0; i < COUNT_OF(data->counter); i++) {
data->counter[i] = other->counter[i];
}
for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) {
data->tearing_flag[i] = other->tearing_flag[i];
}
for(size_t i = 0; i < COUNT_OF(data->page); i++) {
data->page[i] = other->page[i];
}
data->type = other->type;
data->version = other->version;
data->signature = other->signature;
data->pages_read = other->pages_read;
data->pages_total = other->pages_total;
data->auth_attempts = other->auth_attempts;
}
static const char*
mf_ultralight_get_device_name_by_type(MfUltralightType type, NfcDeviceNameType name_type) {
if(name_type == NfcDeviceNameTypeShort &&
(type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21)) {
type = MfUltralightTypeUnknown;
}
return mf_ultralight_features[type].device_name;
}
bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type) {
furi_assert(data);
bool verified = false;
for(MfUltralightType i = 0; i < MfUltralightTypeNum; i++) {
const char* name = mf_ultralight_get_device_name_by_type(i, NfcDeviceNameTypeFull);
verified = furi_string_equal(device_type, name);
if(verified) {
data->type = i;
break;
}
}
return verified;
}
bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version) {
furi_assert(data);
FuriString* temp_str = furi_string_alloc();
bool parsed = false;
do {
// Read ISO14443_3A data
if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break;
// Read Ultralight specific data
// Read Mifare Ultralight format version
uint32_t data_format_version = 0;
if(!flipper_format_read_uint32(
ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &data_format_version, 1)) {
if(!flipper_format_rewind(ff)) break;
}
// Read Mifare Ultralight type
if(data_format_version > 1) {
if(!flipper_format_read_string(ff, MF_ULTRALIGHT_TYPE_KEY, temp_str)) break;
if(!mf_ultralight_verify(data, temp_str)) break;
}
// Read signature
if(!flipper_format_read_hex(
ff,
MF_ULTRALIGHT_SIGNATURE_KEY,
data->signature.data,
sizeof(MfUltralightSignature)))
break;
// Read Mifare version
if(!flipper_format_read_hex(
ff,
MF_ULTRALIGHT_MIFARE_VERSION_KEY,
(uint8_t*)&data->version,
sizeof(MfUltralightVersion)))
break;
// Read counters and tearing flags
bool counters_parsed = true;
for(size_t i = 0; i < 3; i++) {
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i);
if(!flipper_format_read_uint32(
ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) {
counters_parsed = false;
break;
}
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i);
if(!flipper_format_read_hex(
ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) {
counters_parsed = false;
break;
}
}
if(!counters_parsed) break;
// Read pages
uint32_t pages_total = 0;
if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break;
uint32_t pages_read = 0;
if(data_format_version < mf_ultralight_data_format_version) {
pages_read = pages_total;
} else {
if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1))
break;
}
data->pages_total = pages_total;
data->pages_read = pages_read;
if((pages_read > MF_ULTRALIGHT_MAX_PAGE_NUM) || (pages_total > MF_ULTRALIGHT_MAX_PAGE_NUM))
break;
bool pages_parsed = true;
for(size_t i = 0; i < pages_total; i++) {
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i);
if(!flipper_format_read_hex(
ff,
furi_string_get_cstr(temp_str),
data->page[i].data,
sizeof(MfUltralightPage))) {
pages_parsed = false;
break;
}
}
if(!pages_parsed) break;
// Read authentication counter
if(!flipper_format_read_uint32(
ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1)) {
data->auth_attempts = 0;
}
parsed = true;
} while(false);
furi_string_free(temp_str);
return parsed;
}
bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff) {
furi_assert(data);
FuriString* temp_str = furi_string_alloc();
bool saved = false;
do {
if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break;
if(!flipper_format_write_comment_cstr(ff, MF_ULTRALIGHT_PROTOCOL_NAME " specific data"))
break;
if(!flipper_format_write_uint32(
ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &mf_ultralight_data_format_version, 1))
break;
const char* device_type_name =
mf_ultralight_get_device_name_by_type(data->type, NfcDeviceNameTypeFull);
if(!flipper_format_write_string_cstr(ff, MF_ULTRALIGHT_TYPE_KEY, device_type_name)) break;
if(!flipper_format_write_hex(
ff,
MF_ULTRALIGHT_SIGNATURE_KEY,
data->signature.data,
sizeof(MfUltralightSignature)))
break;
if(!flipper_format_write_hex(
ff,
MF_ULTRALIGHT_MIFARE_VERSION_KEY,
(uint8_t*)&data->version,
sizeof(MfUltralightVersion)))
break;
// Write conters and tearing flags data
bool counters_saved = true;
for(size_t i = 0; i < 3; i++) {
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i);
if(!flipper_format_write_uint32(
ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) {
counters_saved = false;
break;
}
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i);
if(!flipper_format_write_hex(
ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) {
counters_saved = false;
break;
}
}
if(!counters_saved) break;
// Write pages data
uint32_t pages_total = data->pages_total;
uint32_t pages_read = data->pages_read;
if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break;
if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) break;
bool pages_saved = true;
for(size_t i = 0; i < data->pages_total; i++) {
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i);
if(!flipper_format_write_hex(
ff,
furi_string_get_cstr(temp_str),
data->page[i].data,
sizeof(MfUltralightPage))) {
pages_saved = false;
break;
}
}
if(!pages_saved) break;
// Write authentication counter
if(!flipper_format_write_uint32(
ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1))
break;
saved = true;
} while(false);
furi_string_free(temp_str);
return saved;
}
bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other) {
bool is_equal = false;
bool data_array_is_equal = true;
do {
if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break;
if(data->type != other->type) break;
if(data->pages_read != other->pages_read) break;
if(data->pages_total != other->pages_total) break;
if(data->auth_attempts != other->auth_attempts) break;
if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break;
if(memcmp(&data->signature, &other->signature, sizeof(data->signature)) != 0) break;
for(size_t i = 0; i < COUNT_OF(data->counter); i++) {
if(memcmp(&data->counter[i], &other->counter[i], sizeof(data->counter[i])) != 0) {
data_array_is_equal = false;
break;
}
}
if(!data_array_is_equal) break;
for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) {
if(memcmp(
&data->tearing_flag[i],
&other->tearing_flag[i],
sizeof(data->tearing_flag[i])) != 0) {
data_array_is_equal = false;
break;
}
}
if(!data_array_is_equal) break;
for(size_t i = 0; i < COUNT_OF(data->page); i++) {
if(memcmp(&data->page[i], &other->page[i], sizeof(data->page[i])) != 0) {
data_array_is_equal = false;
break;
}
}
if(!data_array_is_equal) break;
is_equal = true;
} while(false);
return is_equal;
}
const char*
mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type) {
furi_assert(data);
furi_assert(data->type < MfUltralightTypeNum);
return mf_ultralight_get_device_name_by_type(data->type, name_type);
}
const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len) {
furi_assert(data);
return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len);
}
bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) {
furi_assert(data);
return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len);
}
Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) {
furi_assert(data);
return data->iso14443_3a_data;
}
MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version) {
furi_assert(version);
MfUltralightType type = MfUltralightTypeUnknown;
if(version->storage_size == 0x0B || version->storage_size == 0x00) {
type = MfUltralightTypeUL11;
} else if(version->storage_size == 0x0E) {
type = MfUltralightTypeUL21;
} else if(version->storage_size == 0x0F) {
type = MfUltralightTypeNTAG213;
} else if(version->storage_size == 0x11) {
type = MfUltralightTypeNTAG215;
} else if(version->prod_subtype == 5 && version->prod_ver_major == 2) {
if(version->prod_ver_minor == 1) {
if(version->storage_size == 0x13) {
type = MfUltralightTypeNTAGI2C1K;
} else if(version->storage_size == 0x15) {
type = MfUltralightTypeNTAGI2C2K;
}
} else if(version->prod_ver_minor == 2) {
if(version->storage_size == 0x13) {
type = MfUltralightTypeNTAGI2CPlus1K;
} else if(version->storage_size == 0x15) {
type = MfUltralightTypeNTAGI2CPlus2K;
}
}
} else if(version->storage_size == 0x13) {
type = MfUltralightTypeNTAG216;
}
return type;
}
uint16_t mf_ultralight_get_pages_total(MfUltralightType type) {
return mf_ultralight_features[type].total_pages;
}
uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type) {
return mf_ultralight_features[type].feature_set;
}
bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data) {
furi_assert(iso14443_3a_data);
bool mfu_detected = (iso14443_3a_data->atqa[0] == 0x44) &&
(iso14443_3a_data->atqa[1] == 0x00) && (iso14443_3a_data->sak == 0x00);
return mfu_detected;
}
uint16_t mf_ultralight_get_config_page_num(MfUltralightType type) {
return mf_ultralight_features[type].config_page;
}
uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type) {
uint8_t config_page = mf_ultralight_features[type].config_page;
return (config_page != 0) ? config_page + 2 : 0;
}
bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page) {
uint8_t pwd_page = mf_ultralight_get_pwd_page_num(type);
uint8_t pack_page = pwd_page + 1;
return ((pwd_page != 0) && (page == pwd_page || page == pack_page));
}
bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check) {
return (feature_set & features_to_check) != 0;
}
bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config) {
furi_assert(data);
furi_assert(config);
bool config_pages_found = false;
uint16_t config_page = mf_ultralight_features[data->type].config_page;
if(config_page != 0) {
*config = (MfUltralightConfigPages*)&data->page[config_page]; //-V1027
config_pages_found = true;
}
return config_pages_found;
}
bool mf_ultralight_is_all_data_read(const MfUltralightData* data) {
furi_assert(data);
bool all_read = false;
if(data->pages_read == data->pages_total ||
(data->type == MfUltralightTypeMfulC && data->pages_read == data->pages_total - 4)) {
// Having read all the pages doesn't mean that we've got everything.
// By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000,
// so a default read on an auth-supported NTAG is never complete.
uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type);
if(!mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportPasswordAuth)) {
all_read = true;
} else {
MfUltralightConfigPages* config = NULL;
if(mf_ultralight_get_config_page(data, &config)) {
uint32_t pass =
nfc_util_bytes2num(config->password.data, sizeof(MfUltralightAuthPassword));
uint16_t pack =
nfc_util_bytes2num(config->pack.data, sizeof(MfUltralightAuthPack));
all_read = ((pass != 0) || (pack != 0));
}
}
}
return all_read;
}
bool mf_ultralight_is_counter_configured(const MfUltralightData* data) {
furi_assert(data);
MfUltralightConfigPages* config = NULL;
bool configured = false;
switch(data->type) {
case MfUltralightTypeNTAG213:
case MfUltralightTypeNTAG215:
case MfUltralightTypeNTAG216:
if(mf_ultralight_get_config_page(data, &config)) {
configured = config->access.nfc_cnt_en;
}
break;
default:
configured = true;
break;
}
return configured;
}
@@ -0,0 +1,229 @@
#pragma once
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MF_ULTRALIGHT_TEARING_FLAG_DEFAULT (0xBD)
#define MF_ULTRALIGHT_CMD_GET_VERSION (0x60)
#define MF_ULTRALIGHT_CMD_READ_PAGE (0x30)
#define MF_ULTRALIGHT_CMD_FAST_READ (0x3A)
#define MF_ULTRALIGHT_CMD_SECTOR_SELECT (0xC2)
#define MF_ULTRALIGHT_CMD_COMP_WRITE (0xA0)
#define MF_ULTRALIGHT_CMD_WRITE_PAGE (0xA2)
#define MF_ULTRALIGHT_CMD_FAST_WRITE (0xA6)
#define MF_ULTRALIGHT_CMD_READ_SIG (0x3C)
#define MF_ULTRALIGHT_CMD_READ_CNT (0x39)
#define MF_ULTRALIGHT_CMD_INCR_CNT (0xA5)
#define MF_ULTRALIGHT_CMD_CHECK_TEARING (0x3E)
#define MF_ULTRALIGHT_CMD_PWD_AUTH (0x1B)
#define MF_ULTRALIGHT_CMD_AUTH (0x1A)
#define MF_ULTRALIGHT_CMD_VCSL (0x4B)
#define MF_ULTRALIGHT_CMD_ACK (0x0A)
#define MF_ULTRALIGHT_CMD_NACK (0x00)
#define MF_ULTRALIGHT_CMD_AUTH_NAK (0x04)
#define MF_ULTRALIGHT_MAX_CNTR_VAL (0x00FFFFFF)
#define MF_ULTRALIGHT_MAX_PAGE_NUM (510)
#define MF_ULTRALIGHT_PAGE_SIZE (4U)
#define MF_ULTRALIGHT_SIGNATURE_SIZE (32)
#define MF_ULTRALIGHT_COUNTER_SIZE (3)
#define MF_ULTRALIGHT_COUNTER_NUM (3)
#define MF_ULTRALIGHT_TEARING_FLAG_SIZE (1)
#define MF_ULTRALIGHT_TEARING_FLAG_NUM (3)
#define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4)
#define MF_ULTRALIGHT_AUTH_PACK_SIZE (2)
#define MF_ULTRALIGHT_AUTH_RESPONSE_SIZE (9)
typedef enum {
MfUltralightErrorNone,
MfUltralightErrorNotPresent,
MfUltralightErrorProtocol,
MfUltralightErrorAuth,
MfUltralightErrorTimeout,
} MfUltralightError;
typedef enum {
MfUltralightTypeUnknown,
MfUltralightTypeNTAG203,
MfUltralightTypeMfulC,
MfUltralightTypeUL11,
MfUltralightTypeUL21,
MfUltralightTypeNTAG213,
MfUltralightTypeNTAG215,
MfUltralightTypeNTAG216,
MfUltralightTypeNTAGI2C1K,
MfUltralightTypeNTAGI2C2K,
MfUltralightTypeNTAGI2CPlus1K,
MfUltralightTypeNTAGI2CPlus2K,
MfUltralightTypeNum,
} MfUltralightType;
typedef enum {
MfUltralightFeatureSupportReadVersion = (1U << 0),
MfUltralightFeatureSupportReadSignature = (1U << 1),
MfUltralightFeatureSupportReadCounter = (1U << 2),
MfUltralightFeatureSupportCheckTearingFlag = (1U << 3),
MfUltralightFeatureSupportFastRead = (1U << 4),
MfUltralightFeatureSupportIncCounter = (1U << 5),
MfUltralightFeatureSupportFastWrite = (1U << 6),
MfUltralightFeatureSupportCompatibleWrite = (1U << 7),
MfUltralightFeatureSupportPasswordAuth = (1U << 8),
MfUltralightFeatureSupportVcsl = (1U << 9),
MfUltralightFeatureSupportSectorSelect = (1U << 10),
MfUltralightFeatureSupportSingleCounter = (1U << 11),
MfUltralightFeatureSupportAsciiMirror = (1U << 12),
MfUltralightFeatureSupportCounterInMemory = (1U << 13),
MfUltralightFeatureSupportDynamicLock = (1U << 14),
MfUltralightFeatureSupportAuthenticate = (1U << 15),
} MfUltralightFeatureSupport;
typedef struct {
uint8_t data[MF_ULTRALIGHT_PAGE_SIZE];
} MfUltralightPage;
typedef struct {
MfUltralightPage page[4];
} MfUltralightPageReadCommandData;
typedef struct {
uint8_t header;
uint8_t vendor_id;
uint8_t prod_type;
uint8_t prod_subtype;
uint8_t prod_ver_major;
uint8_t prod_ver_minor;
uint8_t storage_size;
uint8_t protocol_type;
} MfUltralightVersion;
typedef struct {
uint8_t data[MF_ULTRALIGHT_SIGNATURE_SIZE];
} MfUltralightSignature;
typedef union {
uint32_t counter;
uint8_t data[MF_ULTRALIGHT_COUNTER_SIZE];
} MfUltralightCounter;
typedef struct {
uint8_t data;
} MfUltralightTearingFlag;
typedef struct {
uint8_t data[MF_ULTRALIGHT_AUTH_PASSWORD_SIZE];
} MfUltralightAuthPassword;
typedef struct {
uint8_t data[MF_ULTRALIGHT_AUTH_PACK_SIZE];
} MfUltralightAuthPack;
typedef enum {
MfUltralightMirrorNone,
MfUltralightMirrorUid,
MfUltralightMirrorCounter,
MfUltralightMirrorUidCounter,
} MfUltralightMirrorConf;
typedef struct __attribute__((packed)) {
union {
uint8_t value;
struct {
uint8_t rfui1 : 2;
bool strg_mod_en : 1;
bool rfui2 : 1;
uint8_t mirror_byte : 2;
MfUltralightMirrorConf mirror_conf : 2;
};
} mirror;
uint8_t rfui1;
uint8_t mirror_page;
uint8_t auth0;
union {
uint8_t value;
struct {
uint8_t authlim : 3;
bool nfc_cnt_pwd_prot : 1;
bool nfc_cnt_en : 1;
bool nfc_dis_sec1 : 1; // NTAG I2C Plus only
bool cfglck : 1;
bool prot : 1;
};
} access;
uint8_t vctid;
uint8_t rfui2[2];
MfUltralightAuthPassword password;
MfUltralightAuthPack pack;
uint8_t rfui3[2];
} MfUltralightConfigPages;
typedef struct {
Iso14443_3aData* iso14443_3a_data;
MfUltralightType type;
MfUltralightVersion version;
MfUltralightSignature signature;
MfUltralightCounter counter[MF_ULTRALIGHT_COUNTER_NUM];
MfUltralightTearingFlag tearing_flag[MF_ULTRALIGHT_TEARING_FLAG_NUM];
MfUltralightPage page[MF_ULTRALIGHT_MAX_PAGE_NUM];
uint16_t pages_read;
uint16_t pages_total;
uint32_t auth_attempts;
} MfUltralightData;
extern const NfcDeviceBase nfc_device_mf_ultralight;
MfUltralightData* mf_ultralight_alloc();
void mf_ultralight_free(MfUltralightData* data);
void mf_ultralight_reset(MfUltralightData* data);
void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other);
bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type);
bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version);
bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff);
bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other);
const char*
mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type);
const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len);
bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len);
Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data);
MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version);
uint16_t mf_ultralight_get_pages_total(MfUltralightType type);
uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type);
uint16_t mf_ultralight_get_config_page_num(MfUltralightType type);
uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type);
bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page_num);
bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check);
bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config);
bool mf_ultralight_is_all_data_read(const MfUltralightData* data);
bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data);
bool mf_ultralight_is_counter_configured(const MfUltralightData* data);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,772 @@
#include "mf_ultralight_listener_i.h"
#include "mf_ultralight_listener_defs.h"
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h>
#include <furi.h>
#define TAG "MfUltralightListener"
#define MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE (256)
typedef enum {
MfUltralightListenerAccessTypeRead,
MfUltralightListenerAccessTypeWrite,
} MfUltralightListenerAccessType;
typedef struct {
uint8_t cmd;
size_t cmd_len_bits;
MfUltralightListenerCommandCallback callback;
} MfUltralightListenerCmdHandler;
static bool mf_ultralight_listener_check_access(
MfUltralightListener* instance,
uint16_t start_page,
MfUltralightListenerAccessType access_type) {
bool access_success = false;
bool is_write_op = (access_type == MfUltralightListenerAccessTypeWrite);
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportPasswordAuth)) {
access_success = true;
break;
}
if(instance->auth_state != MfUltralightListenerAuthStateSuccess) {
if((instance->config->auth0 <= start_page) &&
(instance->config->access.prot || is_write_op)) {
break;
}
}
if(instance->config->access.cfglck && is_write_op) {
uint16_t config_page_start = instance->data->pages_total - 4;
if((start_page == config_page_start) || (start_page == config_page_start + 1)) {
break;
}
}
access_success = true;
} while(false);
return access_success;
}
static void mf_ultralight_listener_send_short_resp(MfUltralightListener* instance, uint8_t data) {
furi_assert(instance->tx_buffer);
bit_buffer_set_size(instance->tx_buffer, 4);
bit_buffer_set_byte(instance->tx_buffer, 0, data);
iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_buffer);
};
static void mf_ultralight_listener_perform_read(
MfUltralightPage* pages,
MfUltralightListener* instance,
uint16_t start_page,
uint8_t page_cnt,
bool do_i2c_page_check) {
uint16_t pages_total = instance->data->pages_total;
mf_ultralight_mirror_read_prepare(start_page, instance);
for(uint8_t i = 0, rollover = 0; i < page_cnt; i++) {
uint16_t page = start_page + i;
bool page_restricted = !mf_ultralight_listener_check_access(
instance, page, MfUltralightListenerAccessTypeRead);
if(do_i2c_page_check && !mf_ultralight_i2c_validate_pages(page, page, instance))
memset(pages[i].data, 0, sizeof(MfUltralightPage));
else if(mf_ultralight_is_page_pwd_or_pack(instance->data->type, page))
memset(pages[i].data, 0, sizeof(MfUltralightPage));
else {
if(do_i2c_page_check)
page = mf_ultralight_i2c_provide_page_by_requested(page, instance);
page = page_restricted ? rollover++ : page % pages_total;
pages[i] = instance->data->page[page];
mf_ultralight_mirror_read_handler(page, pages[i].data, instance);
}
}
mf_ultralight_single_counter_try_increase(instance);
}
static MfUltralightCommand mf_ultralight_listener_perform_write(
MfUltralightListener* instance,
const uint8_t* const rx_data,
uint16_t start_page,
bool do_i2c_check) {
MfUltralightCommand command = MfUltralightCommandProcessedACK;
if(start_page < 2 && instance->sector == 0)
command = MfUltralightCommandNotProcessedNAK;
else if(start_page == 2 && instance->sector == 0)
mf_ultralight_static_lock_bytes_write(instance->static_lock, *((uint16_t*)&rx_data[2]));
else if(start_page == 3 && instance->sector == 0)
mf_ultralight_capability_container_write(&instance->data->page[start_page], rx_data);
else if(mf_ultralight_is_page_dynamic_lock(instance, start_page))
mf_ultralight_dynamic_lock_bytes_write(instance->dynamic_lock, *((uint32_t*)rx_data));
else {
uint16_t page = start_page;
if(do_i2c_check) page = mf_ultralight_i2c_provide_page_by_requested(start_page, instance);
memcpy(instance->data->page[page].data, rx_data, sizeof(MfUltralightPage));
}
return command;
}
static MfUltralightCommand
mf_ultralight_listener_read_page_handler(MfUltralightListener* instance, BitBuffer* buffer) {
uint16_t start_page = bit_buffer_get_byte(buffer, 1);
uint16_t pages_total = instance->data->pages_total;
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_READ: %d", start_page);
do {
bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type);
if(do_i2c_check) {
if(!mf_ultralight_i2c_validate_pages(start_page, start_page, instance)) break;
} else if(pages_total < start_page) {
break;
}
if(!mf_ultralight_listener_check_access(
instance, start_page, MfUltralightListenerAccessTypeRead)) {
break;
}
MfUltralightPage pages[4] = {};
mf_ultralight_listener_perform_read(pages, instance, start_page, 4, do_i2c_check);
bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, sizeof(pages));
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_listener_fast_read_handler(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedSilent;
FURI_LOG_D(TAG, "CMD_FAST_READ");
do {
if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastRead))
break;
uint16_t pages_total = instance->data->pages_total;
uint16_t start_page = bit_buffer_get_byte(buffer, 1);
uint16_t end_page = bit_buffer_get_byte(buffer, 2);
bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type);
if(do_i2c_check) {
if(!mf_ultralight_i2c_validate_pages(start_page, end_page, instance)) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
} else if(end_page > pages_total - 1) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
if(end_page < start_page) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
if(!mf_ultralight_listener_check_access(
instance, start_page, MfUltralightListenerAccessTypeRead) ||
!mf_ultralight_listener_check_access(
instance, end_page, MfUltralightListenerAccessTypeRead)) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
MfUltralightPage pages[64] = {};
uint8_t page_cnt = (end_page - start_page) + 1;
mf_ultralight_listener_perform_read(pages, instance, start_page, page_cnt, do_i2c_check);
bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, page_cnt * 4);
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_listener_write_page_handler(MfUltralightListener* instance, BitBuffer* buffer) {
uint8_t start_page = bit_buffer_get_byte(buffer, 1);
uint16_t pages_total = instance->data->pages_total;
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_WRITE");
do {
bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type);
if(do_i2c_check) {
if(!mf_ultralight_i2c_validate_pages(start_page, start_page, instance)) break;
} else if(pages_total < start_page) {
break;
}
if(!mf_ultralight_listener_check_access(
instance, start_page, MfUltralightListenerAccessTypeWrite))
break;
if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break;
if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break;
const uint8_t* rx_data = bit_buffer_get_data(buffer);
command =
mf_ultralight_listener_perform_write(instance, &rx_data[2], start_page, do_i2c_check);
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_listener_fast_write_handler(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedSilent;
FURI_LOG_D(TAG, "CMD_FAST_WRITE");
do {
if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportFastWrite))
break;
uint8_t start_page = bit_buffer_get_byte(buffer, 1);
uint8_t end_page = bit_buffer_get_byte(buffer, 2);
if(start_page != 0xF0 || end_page != 0xFF) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
// No SRAM emulation support
command = MfUltralightCommandProcessedACK;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_listener_read_version_handler(MfUltralightListener* instance, BitBuffer* buffer) {
UNUSED(buffer);
MfUltralightCommand command = MfUltralightCommandNotProcessedSilent;
FURI_LOG_D(TAG, "CMD_GET_VERSION");
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadVersion)) {
bit_buffer_copy_bytes(
instance->tx_buffer, (uint8_t*)&instance->data->version, sizeof(MfUltralightVersion));
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
}
return command;
}
static MfUltralightCommand mf_ultralight_listener_read_signature_handler(
MfUltralightListener* instance,
BitBuffer* buffer) {
UNUSED(buffer);
MfUltralightCommand command = MfUltralightCommandNotProcessedSilent;
FURI_LOG_D(TAG, "CMD_READ_SIG");
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportReadSignature)) {
bit_buffer_copy_bytes(
instance->tx_buffer, instance->data->signature.data, sizeof(MfUltralightSignature));
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
}
return command;
}
static MfUltralightCommand
mf_ultralight_listener_read_counter_handler(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_READ_CNT");
do {
uint8_t counter_num = bit_buffer_get_byte(buffer, 1);
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportReadCounter))
break;
if(mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportSingleCounter)) {
if(instance->config == NULL) break;
if(!instance->config->access.nfc_cnt_en || counter_num != 2) break;
if(instance->config->access.nfc_cnt_pwd_prot) {
if(instance->auth_state != MfUltralightListenerAuthStateSuccess) {
break;
}
}
}
if(counter_num > 2) break;
uint8_t cnt_value[3] = {
(instance->data->counter[counter_num].counter >> 0) & 0xff,
(instance->data->counter[counter_num].counter >> 8) & 0xff,
(instance->data->counter[counter_num].counter >> 16) & 0xff,
};
bit_buffer_copy_bytes(instance->tx_buffer, cnt_value, sizeof(cnt_value));
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand mf_ultralight_listener_increase_counter_handler(
MfUltralightListener* instance,
BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_INCR_CNT");
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportIncCounter)) {
command = MfUltralightCommandNotProcessedSilent;
break;
}
uint8_t counter_num = bit_buffer_get_byte(buffer, 1);
if(counter_num > 2) break;
if(instance->data->counter[counter_num].counter == MF_ULTRALIGHT_MAX_CNTR_VAL) {
command = MfUltralightCommandProcessed;
break;
}
MfUltralightCounter buf_counter = {};
bit_buffer_write_bytes_mid(buffer, buf_counter.data, 2, sizeof(buf_counter.data));
uint32_t incr_value = buf_counter.counter;
if(instance->data->counter[counter_num].counter + incr_value > MF_ULTRALIGHT_MAX_CNTR_VAL)
break;
instance->data->counter[counter_num].counter += incr_value;
command = MfUltralightCommandProcessedACK;
} while(false);
return command;
}
static MfUltralightCommand mf_ultralight_listener_check_tearing_handler(
MfUltralightListener* instance,
BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_CHECK_TEARING");
do {
uint8_t tearing_flag_num = bit_buffer_get_byte(buffer, 1);
if(!mf_ultralight_support_feature(
instance->features,
MfUltralightFeatureSupportCheckTearingFlag |
MfUltralightFeatureSupportSingleCounter)) {
break;
}
if(mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportSingleCounter) &&
(tearing_flag_num != 2)) {
break;
}
if(tearing_flag_num >= MF_ULTRALIGHT_TEARING_FLAG_NUM) {
break;
}
bit_buffer_set_size_bytes(instance->tx_buffer, 1);
bit_buffer_set_byte(
instance->tx_buffer, 0, instance->data->tearing_flag[tearing_flag_num].data);
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_listener_vcsl_handler(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedSilent;
UNUSED(instance);
UNUSED(buffer);
FURI_LOG_D(TAG, "CMD_VCSL");
do {
if(!mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportVcsl))
break;
MfUltralightConfigPages* config;
if(!mf_ultralight_get_config_page(instance->data, &config)) break;
bit_buffer_set_size_bytes(instance->tx_buffer, 1);
bit_buffer_set_byte(instance->tx_buffer, 0, config->vctid);
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_listener_auth_handler(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_AUTH");
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportPasswordAuth))
break;
MfUltralightAuthPassword password;
bit_buffer_write_bytes_mid(buffer, password.data, 1, sizeof(password.data));
if(instance->callback) {
instance->mfu_event_data.password = password;
instance->mfu_event.type = MfUltralightListenerEventTypeAuth;
instance->callback(instance->generic_event, instance->context);
}
bool auth_success =
mf_ultralight_auth_check_password(&instance->config->password, &password);
bool card_locked = mf_ultralight_auth_limit_check_and_update(instance, auth_success);
if(card_locked) {
command = MfUltralightCommandNotProcessedAuthNAK;
break;
}
if(!auth_success) break;
bit_buffer_copy_bytes(
instance->tx_buffer, instance->config->pack.data, sizeof(MfUltralightAuthPack));
instance->auth_state = MfUltralightListenerAuthStateSuccess;
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_comp_write_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_D(TAG, "CMD_CM_WR_2");
do {
if(bit_buffer_get_size_bytes(buffer) != 16) break;
const uint8_t* rx_data = bit_buffer_get_data(buffer);
uint8_t start_page = instance->composite_cmd.data;
command = mf_ultralight_listener_perform_write(instance, rx_data, start_page, false);
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_comp_write_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedSilent;
FURI_LOG_D(TAG, "CMD_CM_WR_1");
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportCompatibleWrite))
break;
uint8_t start_page = bit_buffer_get_byte(buffer, 1);
uint16_t last_page = instance->data->pages_total - 1;
if(start_page < 2 || start_page > last_page) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
if(!mf_ultralight_listener_check_access(
instance, start_page, MfUltralightListenerAccessTypeWrite)) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page) ||
mf_ultralight_dynamic_lock_check_page(instance, start_page)) {
command = MfUltralightCommandNotProcessedNAK;
break;
}
instance->composite_cmd.data = start_page;
command = MfUltralightCommandProcessedACK;
mf_ultralight_composite_command_set_next(instance, mf_ultralight_comp_write_handler_p2);
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_sector_select_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
UNUSED(instance);
UNUSED(buffer);
FURI_LOG_D(TAG, "CMD_SEC_SEL_2");
do {
if(bit_buffer_get_size_bytes(buffer) != 4) break;
uint8_t sector = bit_buffer_get_byte(buffer, 0);
if(sector == 0xFF) break;
instance->sector = sector;
command = MfUltralightCommandProcessedSilent;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_sector_select_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
UNUSED(buffer);
FURI_LOG_D(TAG, "CMD_SEC_SEL_1");
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportSectorSelect) &&
bit_buffer_get_byte(buffer, 1) == 0xFF)
break;
command = MfUltralightCommandProcessedACK;
mf_ultralight_composite_command_set_next(instance, mf_ultralight_sector_select_handler_p2);
} while(false);
return command;
}
static const MfUltralightListenerCmdHandler mf_ultralight_command[] = {
{
.cmd = MF_ULTRALIGHT_CMD_READ_PAGE,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_listener_read_page_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_FAST_READ,
.cmd_len_bits = 3 * 8,
.callback = mf_ultralight_listener_fast_read_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_WRITE_PAGE,
.cmd_len_bits = 6 * 8,
.callback = mf_ultralight_listener_write_page_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_FAST_WRITE,
.cmd_len_bits = 67 * 8,
.callback = mf_ultralight_listener_fast_write_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_GET_VERSION,
.cmd_len_bits = 8,
.callback = mf_ultralight_listener_read_version_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_READ_SIG,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_listener_read_signature_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_READ_CNT,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_listener_read_counter_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_CHECK_TEARING,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_listener_check_tearing_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_PWD_AUTH,
.cmd_len_bits = 5 * 8,
.callback = mf_ultralight_listener_auth_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_INCR_CNT,
.cmd_len_bits = 6 * 8,
.callback = mf_ultralight_listener_increase_counter_handler,
},
{
.cmd = MF_ULTRALIGHT_CMD_SECTOR_SELECT,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_sector_select_handler_p1,
},
{
.cmd = MF_ULTRALIGHT_CMD_COMP_WRITE,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_comp_write_handler_p1,
},
{
.cmd = MF_ULTRALIGHT_CMD_VCSL,
.cmd_len_bits = 21 * 8,
.callback = mf_ultralight_listener_vcsl_handler,
},
};
static void mf_ultralight_listener_prepare_emulation(MfUltralightListener* instance) {
MfUltralightData* data = instance->data;
instance->features = mf_ultralight_get_feature_support_set(data->type);
mf_ultralight_get_config_page(data, &instance->config);
mf_ultralight_mirror_prepare_emulation(instance);
mf_ultralight_static_lock_bytes_prepare(instance);
mf_ultralight_dynamic_lock_bytes_prepare(instance);
}
static NfcCommand mf_ultralight_command_postprocess(
MfUltralightCommand mfu_command,
MfUltralightListener* instance) {
NfcCommand command = NfcCommandContinue;
if(mfu_command == MfUltralightCommandProcessedACK) {
mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_ACK);
command = NfcCommandContinue;
} else if(mfu_command == MfUltralightCommandProcessedSilent) {
command = NfcCommandReset;
} else if(mfu_command != MfUltralightCommandProcessed) {
instance->auth_state = MfUltralightListenerAuthStateIdle;
command = NfcCommandSleep;
if(mfu_command == MfUltralightCommandNotProcessedNAK) {
mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_NACK);
} else if(mfu_command == MfUltralightCommandNotProcessedAuthNAK) {
mf_ultralight_listener_send_short_resp(instance, MF_ULTRALIGHT_CMD_AUTH_NAK);
}
}
return command;
}
static NfcCommand mf_ultralight_reset_listener_state(
MfUltralightListener* instance,
Iso14443_3aListenerEventType event_type) {
mf_ultralight_composite_command_reset(instance);
mf_ultralight_single_counter_try_to_unlock(instance, event_type);
instance->sector = 0;
instance->auth_state = MfUltralightListenerAuthStateIdle;
return NfcCommandSleep;
}
MfUltralightListener* mf_ultralight_listener_alloc(
Iso14443_3aListener* iso14443_3a_listener,
MfUltralightData* data) {
furi_assert(iso14443_3a_listener);
MfUltralightListener* instance = malloc(sizeof(MfUltralightListener));
instance->mirror.ascii_mirror_data = furi_string_alloc();
instance->iso14443_3a_listener = iso14443_3a_listener;
instance->data = data;
mf_ultralight_static_lock_bytes_prepare(instance);
mf_ultralight_listener_prepare_emulation(instance);
mf_ultralight_composite_command_reset(instance);
instance->sector = 0;
instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE);
instance->mfu_event.data = &instance->mfu_event_data;
instance->generic_event.protocol = NfcProtocolMfUltralight;
instance->generic_event.instance = instance;
instance->generic_event.event_data = &instance->mfu_event;
return instance;
}
void mf_ultralight_listener_free(MfUltralightListener* instance) {
furi_assert(instance);
furi_assert(instance->data);
furi_assert(instance->tx_buffer);
bit_buffer_free(instance->tx_buffer);
furi_string_free(instance->mirror.ascii_mirror_data);
free(instance);
}
const MfUltralightData* mf_ultralight_listener_get_data(MfUltralightListener* instance) {
furi_assert(instance);
furi_assert(instance->data);
return instance->data;
}
void mf_ultralight_listener_set_callback(
MfUltralightListener* instance,
NfcGenericCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->context = context;
}
NfcCommand mf_ultralight_listener_run(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolIso14443_3a);
furi_assert(event.event_data);
MfUltralightListener* instance = context;
Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data;
BitBuffer* rx_buffer = iso14443_3a_event->data->buffer;
NfcCommand command = NfcCommandContinue;
if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) {
MfUltralightCommand mfu_command = MfUltralightCommandNotFound;
size_t size = bit_buffer_get_size(rx_buffer);
uint8_t cmd = bit_buffer_get_byte(rx_buffer, 0);
if(mf_ultralight_composite_command_in_progress(instance)) {
mfu_command = mf_ultralight_composite_command_run(instance, rx_buffer);
} else {
for(size_t i = 0; i < COUNT_OF(mf_ultralight_command); i++) {
if(size != mf_ultralight_command[i].cmd_len_bits) continue;
if(cmd != mf_ultralight_command[i].cmd) continue;
mfu_command = mf_ultralight_command[i].callback(instance, rx_buffer);
if(mfu_command != MfUltralightCommandNotFound) break;
}
}
command = mf_ultralight_command_postprocess(mfu_command, instance);
} else if(
iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedData ||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff ||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted) {
command = mf_ultralight_reset_listener_state(instance, iso14443_3a_event->type);
}
return command;
}
const NfcListenerBase mf_ultralight_listener = {
.alloc = (NfcListenerAlloc)mf_ultralight_listener_alloc,
.free = (NfcListenerFree)mf_ultralight_listener_free,
.get_data = (NfcListenerGetData)mf_ultralight_listener_get_data,
.set_callback = (NfcListenerSetCallback)mf_ultralight_listener_set_callback,
.run = (NfcListenerRun)mf_ultralight_listener_run,
};
@@ -0,0 +1,28 @@
#pragma once
#include "mf_ultralight.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct MfUltralightListener MfUltralightListener;
typedef enum {
MfUltralightListenerEventTypeAuth,
} MfUltralightListenerEventType;
typedef struct {
union {
MfUltralightAuthPassword password;
};
} MfUltralightListenerEventData;
typedef struct {
MfUltralightListenerEventType type;
MfUltralightListenerEventData* data;
} MfUltralightListenerEvent;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,13 @@
#pragma once
#include <nfc/protocols/nfc_listener_base.h>
#ifdef __cplusplus
extern "C" {
#endif
extern const NfcListenerBase mf_ultralight_listener;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,577 @@
#include "mf_ultralight_listener_i.h"
#include <furi.h>
#define MF_ULTRALIGHT_STATIC_BIT_LOCK_OTP_CC 0
#define MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_9_4 1
#define MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_15_10 2
#define MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, bit) (((lock_bits) & (1U << (bit))) != 0)
#define MF_ULTRALIGHT_BITS_SET(lock_bits, mask) ((lock_bits) |= (mask))
#define MF_ULTRALIGHT_BITS_CLR(lock_bits, mask) ((lock_bits) &= ~(mask))
#define MF_ULTRALIGHT_PAGE_LOCKED(lock_bits, page) MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, page)
#define MF_ULTRALIGHT_STATIC_BIT_OTP_CC_LOCKED(lock_bits) \
MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_OTP_CC)
#define MF_ULTRALIGHT_STATIC_BITS_9_4_LOCKED(lock_bits) \
MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_9_4)
#define MF_ULTRALIGHT_STATIC_BITS_15_10_LOCKED(lock_bits) \
MF_ULTRALIGHT_BIT_ACTIVE(lock_bits, MF_ULTRALIGHT_STATIC_BIT_LOCK_BL_15_10)
#define MF_ULTRALIGHT_STATIC_LOCK_L_OTP_CC_MASK (1U << 3)
#define MF_ULTRALIGHT_STATIC_LOCK_L_9_4_MASK \
((1U << 9) | (1U << 8) | (1U << 7) | (1U << 6) | (1U << 5) | (1U << 4))
#define MF_ULTRALIGHT_STATIC_LOCK_L_15_10_MASK \
((1U << 15) | (1U << 14) | (1U << 13) | (1U << 12) | (1U << 11) | (1U << 10))
#define MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, start, end) (((page) >= (start)) && ((page) <= (end)))
#define MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(page) \
MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x00EC, 0x00ED)
#define MF_ULTRALIGHT_I2C_PAGE_ON_MIRRORED_SESSION_REG(page) \
MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x00F8, 0x00F9)
#define MF_ULTRALIGHT_AUTH_RESET_ATTEMPTS(instance) (instance->data->auth_attempts = 0)
#define MF_ULTRALIGHT_AUTH_INCREASE_ATTEMPTS(instance) (instance->data->auth_attempts++)
static MfUltralightMirrorConf mf_ultralight_mirror_check_mode(
const MfUltralightConfigPages* const config,
const MfUltralightListenerAuthState auth_state) {
MfUltralightMirrorConf mirror_mode = config->mirror.mirror_conf;
if(mirror_mode == MfUltralightMirrorNone || mirror_mode == MfUltralightMirrorUid)
return mirror_mode;
if(!config->access.nfc_cnt_en ||
(config->access.nfc_cnt_pwd_prot && auth_state != MfUltralightListenerAuthStateSuccess)) {
mirror_mode = mirror_mode == MfUltralightMirrorCounter ? MfUltralightMirrorNone :
MfUltralightMirrorUid;
}
return mirror_mode;
}
static bool mf_ultralight_mirror_check_boundaries(MfUltralightListener* instance) {
const MfUltralightConfigPages* const conf = instance->config;
uint8_t last_user_page = mf_ultralight_get_config_page_num(instance->data->type) - 2;
uint8_t max_page_offset = 0;
uint8_t max_byte_offset = 2;
MfUltralightMirrorConf mode = mf_ultralight_mirror_check_mode(conf, instance->auth_state);
bool result = false;
bool again = false;
do {
if(mode == MfUltralightMirrorNone) {
break;
} else if(mode == MfUltralightMirrorUid) {
max_page_offset = 3;
} else if(mode == MfUltralightMirrorCounter) {
max_page_offset = 1;
} else if(mode == MfUltralightMirrorUidCounter) {
max_page_offset = 5;
max_byte_offset = 3;
}
instance->mirror.actual_mode = mode;
if(conf->mirror_page <= 3) break;
if(conf->mirror_page < last_user_page - max_page_offset) {
result = true;
break;
}
if(conf->mirror_page == last_user_page - max_page_offset) {
result = (conf->mirror.mirror_byte <= max_byte_offset);
break;
}
if(conf->mirror_page > last_user_page - max_page_offset &&
mode == MfUltralightMirrorUidCounter) {
mode = MfUltralightMirrorUid;
again = true;
}
} while(again);
return result;
}
static bool mf_ultralight_mirror_enabled(MfUltralightListener* instance) {
bool mirror_enabled = false;
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportAsciiMirror) &&
(instance->config != NULL) && mf_ultralight_mirror_check_boundaries(instance)) {
mirror_enabled = true;
}
instance->mirror.enabled = mirror_enabled;
return instance->mirror.enabled;
}
static uint8_t mf_ultralight_get_mirror_data_size(MfUltralightMirrorConf mode) {
switch(mode) {
case MfUltralightMirrorUid:
return 14;
case MfUltralightMirrorCounter:
return 6;
case MfUltralightMirrorUidCounter:
return 21;
default:
return 0;
}
}
static uint8_t mf_ultralight_get_mirror_last_page(MfUltralightListener* instance) {
uint8_t strSize = mf_ultralight_get_mirror_data_size(instance->mirror.actual_mode);
return (instance->config->mirror_page + 1U + strSize / 4);
}
static uint8_t mf_ultralight_get_ascii_offset(uint8_t start_page, MfUltralightListener* instance) {
uint8_t start_offset = 0;
if(instance->config->mirror.mirror_conf == MfUltralightMirrorCounter) start_offset = 15;
uint8_t ascii_offset = start_offset;
if(start_page > instance->config->mirror_page)
ascii_offset = (start_page - instance->config->mirror_page) * 4 -
instance->config->mirror.mirror_byte + start_offset;
return ascii_offset;
}
static uint8_t mf_ultralight_get_ascii_end(MfUltralightMirrorConf mode) {
return (mode == MfUltralightMirrorUid) ? 14 : 21;
}
static uint8_t mf_ultralight_get_byte_offset(
uint8_t current_page,
const MfUltralightConfigPages* const config) {
return (current_page > config->mirror_page) ? 0 : config->mirror.mirror_byte;
}
static void mf_ultralight_format_mirror_data(
FuriString* str,
const uint8_t* const data,
const uint8_t data_len) {
for(uint8_t i = 0; i < data_len; i++) furi_string_cat_printf(str, "%02X", data[i]);
}
void mf_ultralight_mirror_read_prepare(uint8_t start_page, MfUltralightListener* instance) {
if(mf_ultralight_mirror_enabled(instance)) {
instance->mirror.ascii_offset = mf_ultralight_get_ascii_offset(start_page, instance);
instance->mirror.ascii_end = mf_ultralight_get_ascii_end(instance->mirror.actual_mode);
instance->mirror.mirror_last_page = mf_ultralight_get_mirror_last_page(instance);
}
}
void mf_ultralight_mirror_read_handler(
uint8_t mirror_page_num,
uint8_t* dest,
MfUltralightListener* instance) {
if(instance->mirror.enabled && mirror_page_num >= instance->config->mirror_page &&
mirror_page_num <= instance->mirror.mirror_last_page) {
uint8_t byte_offset = mf_ultralight_get_byte_offset(mirror_page_num, instance->config);
uint8_t ascii_offset = instance->mirror.ascii_offset;
uint8_t ascii_end = instance->mirror.ascii_end;
uint8_t* source = (uint8_t*)furi_string_get_cstr(instance->mirror.ascii_mirror_data);
for(uint8_t j = byte_offset; (j < 4) && (ascii_offset < ascii_end); j++) {
dest[j] = source[ascii_offset];
ascii_offset++;
}
instance->mirror.ascii_offset = ascii_offset;
}
}
void mf_ultralight_mirror_prepare_emulation(MfUltralightListener* instance) {
mf_ultralight_format_mirror_data(
instance->mirror.ascii_mirror_data,
instance->data->iso14443_3a_data->uid,
instance->data->iso14443_3a_data->uid_len);
furi_string_push_back(instance->mirror.ascii_mirror_data, 'x');
mf_ultraligt_mirror_format_counter(instance);
}
void mf_ultraligt_mirror_format_counter(MfUltralightListener* instance) {
furi_string_left(
instance->mirror.ascii_mirror_data, instance->data->iso14443_3a_data->uid_len * 2 + 1);
uint8_t* c = instance->data->counter[2].data;
furi_string_cat_printf(instance->mirror.ascii_mirror_data, "%02X%02X%02X", c[2], c[1], c[0]);
}
bool mf_ultralight_composite_command_in_progress(MfUltralightListener* instance) {
return (instance->composite_cmd.callback != NULL);
}
MfUltralightCommand
mf_ultralight_composite_command_run(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = (instance->composite_cmd.callback)(instance, buffer);
mf_ultralight_composite_command_reset(instance);
return command;
}
void mf_ultralight_composite_command_reset(MfUltralightListener* instance) {
instance->composite_cmd.callback = NULL;
instance->composite_cmd.data = 0;
}
void mf_ultralight_composite_command_set_next(
MfUltralightListener* instance,
const MfUltralightListenerCommandCallback handler) {
instance->composite_cmd.callback = handler;
}
void mf_ultralight_single_counter_try_increase(MfUltralightListener* instance) {
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportSingleCounter) &&
instance->config->access.nfc_cnt_en && !instance->single_counter_increased) {
if(instance->data->counter[2].counter < MF_ULTRALIGHT_MAX_CNTR_VAL) {
instance->data->counter[2].counter++;
mf_ultraligt_mirror_format_counter(instance);
}
instance->single_counter_increased = true;
}
}
void mf_ultralight_single_counter_try_to_unlock(
MfUltralightListener* instance,
Iso14443_3aListenerEventType type) {
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportSingleCounter) &&
type == Iso14443_3aListenerEventTypeFieldOff) {
instance->single_counter_increased = false;
}
}
static bool mf_ultralight_i2c_page_validator_for_sector0(
uint16_t start_page,
uint16_t end_page,
MfUltralightType type) {
UNUSED(type);
bool valid = false;
if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) {
if(start_page <= 0xE9 && end_page <= 0xE9) {
valid = true;
} else if(
MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(start_page) &&
MF_ULTRALIGHT_I2C_PAGE_ON_SESSION_REG(end_page)) {
valid = true;
}
} else if(type == MfUltralightTypeNTAGI2C1K) {
if((start_page <= 0xE2) || MF_ULTRALIGHT_PAGE_IN_BOUNDS(start_page, 0x00E8, 0x00E9)) {
valid = true;
}
} else if(type == MfUltralightTypeNTAGI2C2K) {
valid = (start_page <= 0xFF && end_page <= 0xFF);
}
return valid;
}
static bool mf_ultralight_i2c_page_validator_for_sector1(
uint16_t start_page,
uint16_t end_page,
MfUltralightType type) {
bool valid = false;
if(type == MfUltralightTypeNTAGI2CPlus2K) {
valid = (start_page <= 0xFF && end_page <= 0xFF);
} else if(type == MfUltralightTypeNTAGI2C2K) {
valid = (MF_ULTRALIGHT_PAGE_IN_BOUNDS(start_page, 0x00E8, 0x00E9) || (start_page <= 0xE0));
} else if(type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2CPlus1K) {
valid = false;
}
return valid;
}
static bool mf_ultralight_i2c_page_validator_for_sector2(
uint16_t start_page,
uint16_t end_page,
MfUltralightType type) {
UNUSED(start_page);
UNUSED(end_page);
UNUSED(type);
return false;
}
static bool mf_ultralight_i2c_page_validator_for_sector3(
uint16_t start_page,
uint16_t end_page,
MfUltralightType type) {
UNUSED(type);
UNUSED(end_page);
return MF_ULTRALIGHT_I2C_PAGE_ON_MIRRORED_SESSION_REG(start_page);
}
typedef bool (
*MfUltralightI2CValidator)(uint16_t start_page, uint16_t end_page, MfUltralightType type);
typedef uint16_t (*MfUltralightI2CPageProvider)(uint16_t page, MfUltralightType type);
const MfUltralightI2CValidator validation_methods[] = {
mf_ultralight_i2c_page_validator_for_sector0,
mf_ultralight_i2c_page_validator_for_sector1,
mf_ultralight_i2c_page_validator_for_sector2,
mf_ultralight_i2c_page_validator_for_sector3,
};
bool mf_ultralight_i2c_validate_pages(
uint16_t start_page,
uint16_t end_page,
MfUltralightListener* instance) {
bool valid = false;
if(instance->sector < COUNT_OF(validation_methods)) {
MfUltralightI2CValidator validate = validation_methods[instance->sector];
valid = validate(start_page, end_page, instance->data->type);
}
return valid;
}
bool mf_ultralight_is_i2c_tag(MfUltralightType type) {
return type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2C2K ||
type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K;
}
static uint16_t mf_ultralight_i2c_page_provider_for_sector0(uint16_t page, MfUltralightType type) {
uint8_t new_page = page;
if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) {
if(page == 0x00EC) {
new_page = 234;
} else if(page == 0x00ED) {
new_page = 235;
}
} else if(type == MfUltralightTypeNTAGI2C1K) {
if(page == 0x00E8) {
new_page = 232;
} else if(page == 0x00E9) {
new_page = 233;
}
} else if(type == MfUltralightTypeNTAGI2C2K) {
new_page = page;
}
return new_page;
}
static uint16_t mf_ultralight_i2c_page_provider_for_sector1(uint16_t page, MfUltralightType type) {
UNUSED(type);
uint16_t new_page = page;
if(type == MfUltralightTypeNTAGI2CPlus2K) new_page = page + 236;
if(type == MfUltralightTypeNTAGI2C2K) new_page = page + 256;
return new_page;
}
static uint16_t mf_ultralight_i2c_page_provider_for_sector2(uint16_t page, MfUltralightType type) {
UNUSED(type);
return page;
}
static uint16_t mf_ultralight_i2c_page_provider_for_sector3(uint16_t page, MfUltralightType type) {
uint16_t new_page = page;
if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) {
if(page == 0x00F8)
new_page = 234;
else if(page == 0x00F9)
new_page = 235;
} else if(type == MfUltralightTypeNTAGI2C1K || type == MfUltralightTypeNTAGI2C2K) {
if(page == 0x00F8)
new_page = (type == MfUltralightTypeNTAGI2C1K) ? 227 : 481;
else if(page == 0x00F9)
new_page = (type == MfUltralightTypeNTAGI2C1K) ? 228 : 482;
}
return new_page;
}
const MfUltralightI2CPageProvider provider_methods[] = {
mf_ultralight_i2c_page_provider_for_sector0,
mf_ultralight_i2c_page_provider_for_sector1,
mf_ultralight_i2c_page_provider_for_sector2,
mf_ultralight_i2c_page_provider_for_sector3,
};
uint16_t
mf_ultralight_i2c_provide_page_by_requested(uint16_t page, MfUltralightListener* instance) {
uint16_t result = page;
if(instance->sector < COUNT_OF(provider_methods)) {
MfUltralightI2CPageProvider provider = provider_methods[instance->sector];
result = provider(page, instance->data->type);
}
return result;
}
void mf_ultralight_static_lock_bytes_prepare(MfUltralightListener* instance) {
instance->static_lock = (uint16_t*)&instance->data->page[2].data[2];
}
void mf_ultralight_static_lock_bytes_write(
MfUltralightStaticLockData* const lock_bits,
uint16_t new_bits) {
uint16_t current_locks = *lock_bits;
if(MF_ULTRALIGHT_STATIC_BIT_OTP_CC_LOCKED(current_locks))
MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_OTP_CC_MASK);
if(MF_ULTRALIGHT_STATIC_BITS_9_4_LOCKED(current_locks))
MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_9_4_MASK);
if(MF_ULTRALIGHT_STATIC_BITS_15_10_LOCKED(current_locks))
MF_ULTRALIGHT_BITS_CLR(new_bits, MF_ULTRALIGHT_STATIC_LOCK_L_15_10_MASK);
MF_ULTRALIGHT_BITS_SET(current_locks, new_bits);
*lock_bits = current_locks;
}
bool mf_ultralight_static_lock_check_page(
const MfUltralightStaticLockData* const lock_bits,
uint16_t page) {
bool locked = false;
if(MF_ULTRALIGHT_PAGE_IN_BOUNDS(page, 0x0003, 0x000F)) {
uint16_t current_locks = *lock_bits;
locked = MF_ULTRALIGHT_PAGE_LOCKED(current_locks, page);
}
return locked;
}
void mf_ultralight_capability_container_write(
MfUltralightPage* const current_page,
const uint8_t* const new_data) {
for(uint8_t i = 0; i < MF_ULTRALIGHT_PAGE_SIZE; i++) {
current_page->data[i] |= new_data[i];
}
}
static uint16_t mf_ultralight_dynamic_lock_page_num(const MfUltralightData* data) {
uint16_t lock_page;
if(data->type == MfUltralightTypeNTAGI2C1K)
lock_page = 226;
else if(data->type == MfUltralightTypeNTAGI2C2K)
lock_page = 480;
else
lock_page = mf_ultralight_get_config_page_num(data->type) - 1;
return lock_page;
}
void mf_ultralight_dynamic_lock_bytes_prepare(MfUltralightListener* instance) {
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock)) {
uint16_t lock_page = mf_ultralight_dynamic_lock_page_num(instance->data);
instance->dynamic_lock = (uint32_t*)instance->data->page[lock_page].data;
} else {
instance->dynamic_lock = NULL;
}
}
bool mf_ultralight_is_page_dynamic_lock(const MfUltralightListener* instance, uint16_t page) {
bool is_lock = false;
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock)) {
uint16_t linear_page = page + instance->sector * 256;
uint16_t lock_page = mf_ultralight_dynamic_lock_page_num(instance->data);
is_lock = linear_page == lock_page;
}
return is_lock;
}
void mf_ultralight_dynamic_lock_bytes_write(
MfUltralightDynamicLockData* const lock_bits,
uint32_t new_bits) {
furi_assert(lock_bits != NULL);
new_bits &= 0x00FFFFFF;
uint32_t current_lock = *lock_bits;
for(uint8_t i = 0; i < 8; i++) {
uint8_t bl_bit = i + 16;
if(MF_ULTRALIGHT_BIT_ACTIVE(current_lock, bl_bit)) {
uint8_t lock_bit = i * 2;
uint32_t mask = (1U << lock_bit) | (1U << (lock_bit + 1));
MF_ULTRALIGHT_BITS_CLR(new_bits, mask);
}
}
MF_ULTRALIGHT_BITS_SET(current_lock, new_bits);
*lock_bits = current_lock;
}
static uint8_t mf_ultralight_dynamic_lock_granularity(MfUltralightType type) {
switch(type) {
case MfUltralightTypeUL21:
case MfUltralightTypeNTAG213:
return 2;
case MfUltralightTypeNTAG215:
case MfUltralightTypeNTAG216:
case MfUltralightTypeNTAGI2C1K:
case MfUltralightTypeNTAGI2CPlus1K:
return 16;
case MfUltralightTypeNTAGI2C2K:
case MfUltralightTypeNTAGI2CPlus2K:
return 32;
default:
return 1;
}
}
static uint16_t mf_ultralight_get_upper_page_bound(MfUltralightType type) {
uint16_t upper_page_bound;
if(type == MfUltralightTypeNTAGI2CPlus2K)
upper_page_bound = 511;
else if(type == MfUltralightTypeNTAGI2C2K)
upper_page_bound = 479;
else {
upper_page_bound = mf_ultralight_get_config_page_num(type) - 2;
}
return upper_page_bound;
}
bool mf_ultralight_dynamic_lock_check_page(const MfUltralightListener* instance, uint16_t page) {
UNUSED(page);
bool locked = false;
uint16_t upper_page_bound = mf_ultralight_get_upper_page_bound(instance->data->type);
uint16_t linear_page = page + instance->sector * 256;
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportDynamicLock) &&
MF_ULTRALIGHT_PAGE_IN_BOUNDS(linear_page, 0x0010, upper_page_bound)) {
uint8_t granularity = mf_ultralight_dynamic_lock_granularity(instance->data->type);
uint8_t bit = (linear_page - 16) / granularity;
uint16_t current_locks = *instance->dynamic_lock;
locked = MF_ULTRALIGHT_PAGE_LOCKED(current_locks, bit);
}
return locked;
}
static bool mf_ultralight_auth_check_attempts(const MfUltralightListener* instance) {
uint8_t authlim = ((instance->data->type == MfUltralightTypeNTAGI2CPlus1K) ||
(instance->data->type == MfUltralightTypeNTAGI2CPlus2K)) ?
(1U << instance->config->access.authlim) :
instance->config->access.authlim;
return (instance->data->auth_attempts >= authlim);
}
bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, bool auth_success) {
bool card_locked = false;
do {
if(instance->config->access.authlim == 0) break;
card_locked = mf_ultralight_auth_check_attempts(instance);
if(card_locked) break;
if(auth_success) {
MF_ULTRALIGHT_AUTH_RESET_ATTEMPTS(instance);
} else {
MF_ULTRALIGHT_AUTH_INCREASE_ATTEMPTS(instance);
}
card_locked = mf_ultralight_auth_check_attempts(instance);
} while(false);
return card_locked;
}
bool mf_ultralight_auth_check_password(
const MfUltralightAuthPassword* config_pass,
const MfUltralightAuthPassword* auth_pass) {
return memcmp(config_pass->data, auth_pass->data, sizeof(MfUltralightAuthPassword)) == 0;
}
@@ -0,0 +1,123 @@
#pragma once
#include "mf_ultralight_listener.h"
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h>
#include <nfc/protocols/nfc_generic_event.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
MfUltralightListenerAuthStateIdle,
MfUltralightListenerAuthStateSuccess,
} MfUltralightListenerAuthState;
typedef enum {
MfUltralightCommandNotFound,
MfUltralightCommandProcessed,
MfUltralightCommandProcessedACK,
MfUltralightCommandProcessedSilent,
MfUltralightCommandNotProcessedNAK,
MfUltralightCommandNotProcessedSilent,
MfUltralightCommandNotProcessedAuthNAK,
} MfUltralightCommand;
typedef MfUltralightCommand (
*MfUltralightListenerCommandCallback)(MfUltralightListener* instance, BitBuffer* buf);
typedef uint8_t MfUltralightListenerCompositeCommandData;
typedef struct {
MfUltralightListenerCompositeCommandData data;
MfUltralightListenerCommandCallback callback;
} MfUltralightListenerCompositeCommandContext;
typedef struct {
uint8_t enabled;
uint8_t ascii_offset;
uint8_t ascii_end;
uint8_t mirror_last_page;
MfUltralightMirrorConf actual_mode;
FuriString* ascii_mirror_data;
} MfUltralightMirrorMode;
typedef uint16_t MfUltralightStaticLockData;
typedef uint32_t MfUltralightDynamicLockData;
struct MfUltralightListener {
Iso14443_3aListener* iso14443_3a_listener;
MfUltralightListenerAuthState auth_state;
MfUltralightData* data;
BitBuffer* tx_buffer;
MfUltralightFeatureSupport features;
MfUltralightConfigPages* config;
MfUltralightStaticLockData* static_lock;
MfUltralightDynamicLockData* dynamic_lock;
NfcGenericEvent generic_event;
MfUltralightListenerEvent mfu_event;
MfUltralightListenerEventData mfu_event_data;
NfcGenericCallback callback;
uint8_t sector;
bool single_counter_increased;
MfUltralightMirrorMode mirror;
MfUltralightListenerCompositeCommandContext composite_cmd;
void* context;
};
void mf_ultralight_single_counter_try_increase(MfUltralightListener* instance);
void mf_ultralight_single_counter_try_to_unlock(
MfUltralightListener* instance,
Iso14443_3aListenerEventType type);
void mf_ultralight_mirror_prepare_emulation(MfUltralightListener* instance);
void mf_ultraligt_mirror_format_counter(MfUltralightListener* instance);
void mf_ultralight_mirror_read_prepare(uint8_t start_page, MfUltralightListener* instance);
void mf_ultralight_mirror_read_handler(
uint8_t mirror_page_num,
uint8_t* dest,
MfUltralightListener* instance);
void mf_ultralight_composite_command_set_next(
MfUltralightListener* instance,
const MfUltralightListenerCommandCallback handler);
void mf_ultralight_composite_command_reset(MfUltralightListener* instance);
bool mf_ultralight_composite_command_in_progress(MfUltralightListener* instance);
MfUltralightCommand
mf_ultralight_composite_command_run(MfUltralightListener* instance, BitBuffer* buffer);
bool mf_ultralight_is_i2c_tag(MfUltralightType type);
bool mf_ultralight_i2c_validate_pages(
uint16_t start_page,
uint16_t end_page,
MfUltralightListener* instance);
uint16_t
mf_ultralight_i2c_provide_page_by_requested(uint16_t page, MfUltralightListener* instance);
void mf_ultralight_static_lock_bytes_prepare(MfUltralightListener* instance);
void mf_ultralight_static_lock_bytes_write(
MfUltralightStaticLockData* const lock_bits,
uint16_t new_bits);
bool mf_ultralight_static_lock_check_page(
const MfUltralightStaticLockData* const lock_bits,
uint16_t page);
void mf_ultralight_capability_container_write(
MfUltralightPage* const current_page,
const uint8_t* const new_data);
void mf_ultralight_dynamic_lock_bytes_prepare(MfUltralightListener* instance);
bool mf_ultralight_is_page_dynamic_lock(const MfUltralightListener* instance, uint16_t start_page);
void mf_ultralight_dynamic_lock_bytes_write(
MfUltralightDynamicLockData* const lock_bits,
uint32_t new_bits);
bool mf_ultralight_dynamic_lock_check_page(const MfUltralightListener* instance, uint16_t page);
bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, bool auth_success);
bool mf_ultralight_auth_check_password(
const MfUltralightAuthPassword* config_pass,
const MfUltralightAuthPassword* auth_pass);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,591 @@
#include "mf_ultralight_poller_i.h"
#include <nfc/protocols/nfc_poller_base.h>
#include <furi.h>
#define TAG "MfUltralightPoller"
typedef NfcCommand (*MfUltralightPollerReadHandler)(MfUltralightPoller* instance);
static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k(
uint16_t lin_addr,
uint8_t* sector,
uint8_t* tag,
uint8_t* pages_left) {
bool tag_calculated = false;
// 0 - 226: sector 0
// 227 - 228: config registers
// 229 - 230: session registers
if(lin_addr > 230) {
*pages_left = 0;
} else if(lin_addr >= 229) {
*sector = 3;
*pages_left = 2 - (lin_addr - 229);
*tag = lin_addr - 229 + 248;
tag_calculated = true;
} else if(lin_addr >= 227) {
*sector = 0;
*pages_left = 2 - (lin_addr - 227);
*tag = lin_addr - 227 + 232;
tag_calculated = true;
} else {
*sector = 0;
*pages_left = 227 - lin_addr;
*tag = lin_addr;
tag_calculated = true;
}
return tag_calculated;
}
static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k(
uint16_t lin_addr,
uint8_t* sector,
uint8_t* tag,
uint8_t* pages_left) {
bool tag_calculated = false;
// 0 - 255: sector 0
// 256 - 480: sector 1
// 481 - 482: config registers
// 483 - 484: session registers
if(lin_addr > 484) {
*pages_left = 0;
} else if(lin_addr >= 483) {
*sector = 3;
*pages_left = 2 - (lin_addr - 483);
*tag = lin_addr - 483 + 248;
tag_calculated = true;
} else if(lin_addr >= 481) {
*sector = 1;
*pages_left = 2 - (lin_addr - 481);
*tag = lin_addr - 481 + 232;
tag_calculated = true;
} else if(lin_addr >= 256) {
*sector = 1;
*pages_left = 225 - (lin_addr - 256);
*tag = lin_addr - 256;
tag_calculated = true;
} else {
*sector = 0;
*pages_left = 256 - lin_addr;
*tag = lin_addr;
tag_calculated = true;
}
return tag_calculated;
}
static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k(
uint16_t lin_addr,
uint8_t* sector,
uint8_t* tag,
uint8_t* pages_left) {
bool tag_calculated = false;
// 0 - 233: sector 0 + registers
// 234 - 235: session registers
if(lin_addr > 235) {
*pages_left = 0;
} else if(lin_addr >= 234) {
*sector = 0;
*pages_left = 2 - (lin_addr - 234);
*tag = lin_addr - 234 + 236;
tag_calculated = true;
} else {
*sector = 0;
*pages_left = 234 - lin_addr;
*tag = lin_addr;
tag_calculated = true;
}
return tag_calculated;
}
static bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k(
uint16_t lin_addr,
uint8_t* sector,
uint8_t* tag,
uint8_t* pages_left) {
bool tag_calculated = false;
// 0 - 233: sector 0 + registers
// 234 - 235: session registers
// 236 - 491: sector 1
if(lin_addr > 491) {
*pages_left = 0;
} else if(lin_addr >= 236) {
*sector = 1;
*pages_left = 256 - (lin_addr - 236);
*tag = lin_addr - 236;
tag_calculated = true;
} else if(lin_addr >= 234) {
*sector = 0;
*pages_left = 2 - (lin_addr - 234);
*tag = lin_addr - 234 + 236;
tag_calculated = true;
} else {
*sector = 0;
*pages_left = 234 - lin_addr;
*tag = lin_addr;
tag_calculated = true;
}
return tag_calculated;
}
bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag(
MfUltralightPoller* instance,
uint16_t lin_addr,
uint8_t* sector,
uint8_t* tag,
uint8_t* pages_left) {
furi_assert(instance);
furi_assert(sector);
furi_assert(tag);
furi_assert(pages_left);
bool tag_calculated = false;
if(instance->data->type == MfUltralightTypeNTAGI2C1K) {
tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_1k(
lin_addr, sector, tag, pages_left);
} else if(instance->data->type == MfUltralightTypeNTAGI2C2K) {
tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_2k(
lin_addr, sector, tag, pages_left);
} else if(instance->data->type == MfUltralightTypeNTAGI2CPlus1K) {
tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_1k(
lin_addr, sector, tag, pages_left);
} else if(instance->data->type == MfUltralightTypeNTAGI2CPlus2K) {
tag_calculated = mf_ultralight_poller_ntag_i2c_addr_lin_to_tag_ntag_i2c_plus_2k(
lin_addr, sector, tag, pages_left);
}
return tag_calculated;
}
MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) {
furi_assert(iso14443_3a_poller);
MfUltralightPoller* instance = malloc(sizeof(MfUltralightPoller));
instance->iso14443_3a_poller = iso14443_3a_poller;
instance->tx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE);
instance->rx_buffer = bit_buffer_alloc(MF_ULTRALIGHT_MAX_BUFF_SIZE);
instance->data = mf_ultralight_alloc();
instance->mfu_event.data = &instance->mfu_event_data;
instance->general_event.protocol = NfcProtocolMfUltralight;
instance->general_event.event_data = &instance->mfu_event;
instance->general_event.instance = instance;
return instance;
}
void mf_ultralight_poller_free(MfUltralightPoller* instance) {
furi_assert(instance);
furi_assert(instance->data);
furi_assert(instance->tx_buffer);
furi_assert(instance->rx_buffer);
bit_buffer_free(instance->tx_buffer);
bit_buffer_free(instance->rx_buffer);
mf_ultralight_free(instance->data);
free(instance);
}
static void mf_ultralight_poller_set_callback(
MfUltralightPoller* instance,
NfcGenericCallback callback,
void* context) {
furi_assert(instance);
furi_assert(callback);
instance->callback = callback;
instance->context = context;
}
const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance) {
furi_assert(instance);
return instance->data;
}
static NfcCommand mf_ultralight_poller_handler_idle(MfUltralightPoller* instance) {
bit_buffer_reset(instance->tx_buffer);
bit_buffer_reset(instance->rx_buffer);
iso14443_3a_copy(
instance->data->iso14443_3a_data,
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
instance->counters_read = 0;
instance->counters_total = 3;
instance->tearing_flag_read = 0;
instance->tearing_flag_total = 3;
instance->pages_read = 0;
instance->state = MfUltralightPollerStateReadVersion;
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance) {
instance->error = mf_ultralight_poller_async_read_version(instance, &instance->data->version);
if(instance->error == MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Read version success");
instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version);
instance->state = MfUltralightPollerStateGetFeatureSet;
} else {
FURI_LOG_D(TAG, "Didn't response. Check Ultralight C");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
instance->state = MfUltralightPollerStateDetectMfulC;
}
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) {
instance->error = mf_ultralight_poller_async_authenticate(instance);
if(instance->error == MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Ultralight C detected");
instance->data->type = MfUltralightTypeMfulC;
instance->state = MfUltralightPollerStateGetFeatureSet;
} else {
FURI_LOG_D(TAG, "Didn't response. Check NTAG 203");
instance->state = MfUltralightPollerStateDetectNtag203;
}
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_check_ntag_203(MfUltralightPoller* instance) {
MfUltralightPageReadCommandData data = {};
instance->error = mf_ultralight_poller_async_read_page(instance, 41, &data);
if(instance->error == MfUltralightErrorNone) {
FURI_LOG_D(TAG, "NTAG203 detected");
instance->data->type = MfUltralightTypeNTAG203;
} else {
FURI_LOG_D(TAG, "Original Ultralight detected");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
instance->data->type = MfUltralightTypeUnknown;
}
instance->state = MfUltralightPollerStateGetFeatureSet;
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_get_feature_set(MfUltralightPoller* instance) {
instance->feature_set = mf_ultralight_get_feature_support_set(instance->data->type);
instance->pages_total = mf_ultralight_get_pages_total(instance->data->type);
instance->data->pages_total = instance->pages_total;
FURI_LOG_D(
TAG,
"%s detected. Total pages: %d",
mf_ultralight_get_device_name(instance->data, NfcDeviceNameTypeFull),
instance->pages_total);
instance->state = MfUltralightPollerStateReadSignature;
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller* instance) {
MfUltralightPollerState next_state = MfUltralightPollerStateAuth;
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportReadSignature)) {
FURI_LOG_D(TAG, "Reading signature");
instance->error =
mf_ultralight_poller_async_read_signature(instance, &instance->data->signature);
if(instance->error != MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Read signature failed");
next_state = MfUltralightPollerStateReadFailed;
}
} else {
FURI_LOG_D(TAG, "Skip reading signature");
}
instance->state = next_state;
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_read_counters(MfUltralightPoller* instance) {
do {
if(!mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportReadCounter) ||
!mf_ultralight_is_counter_configured(instance->data)) {
FURI_LOG_D(TAG, "Skip reading counters");
instance->state = MfUltralightPollerStateReadTearingFlags;
break;
}
MfUltralightConfigPages* config = NULL;
mf_ultralight_get_config_page(instance->data, &config);
if(config->access.nfc_cnt_pwd_prot && !instance->auth_context.auth_success) {
FURI_LOG_D(TAG, "Counter reading is protected with password");
instance->state = MfUltralightPollerStateReadTearingFlags;
break;
}
if(instance->counters_read == instance->counters_total) {
instance->state = MfUltralightPollerStateReadTearingFlags;
break;
}
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportSingleCounter)) {
instance->counters_read = 2;
}
FURI_LOG_D(TAG, "Reading counter %d", instance->counters_read);
instance->error = mf_ultralight_poller_async_read_counter(
instance, instance->counters_read, &instance->data->counter[instance->counters_read]);
if(instance->error != MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Failed to read %d counter", instance->counters_read);
instance->state = MfUltralightPollerStateReadTearingFlags;
} else {
instance->counters_read++;
}
} while(false);
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_read_tearing_flags(MfUltralightPoller* instance) {
if(mf_ultralight_support_feature(
instance->feature_set,
MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportSingleCounter)) {
if(instance->tearing_flag_read == instance->tearing_flag_total) {
instance->state = MfUltralightPollerStateTryDefaultPass;
} else {
bool single_counter = mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportSingleCounter);
if(single_counter) instance->tearing_flag_read = 2;
FURI_LOG_D(TAG, "Reading tearing flag %d", instance->tearing_flag_read);
instance->error = mf_ultralight_poller_async_read_tearing_flag(
instance,
instance->tearing_flag_read,
&instance->data->tearing_flag[instance->tearing_flag_read]);
if((instance->error == MfUltralightErrorProtocol) && single_counter) {
instance->tearing_flag_read++;
} else if(instance->error != MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Reading tearing flag %d failed", instance->tearing_flag_read);
instance->state = MfUltralightPollerStateTryDefaultPass;
} else {
instance->tearing_flag_read++;
}
}
} else {
FURI_LOG_D(TAG, "Skip reading tearing flags");
instance->state = MfUltralightPollerStateTryDefaultPass;
}
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance) {
NfcCommand command = NfcCommandContinue;
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportPasswordAuth)) {
instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest;
command = instance->callback(instance->general_event, instance->context);
if(!instance->mfu_event.data->auth_context.skip_auth) {
instance->auth_context.password = instance->mfu_event.data->auth_context.password;
uint32_t pass = nfc_util_bytes2num(
instance->auth_context.password.data, sizeof(MfUltralightAuthPassword));
FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass);
instance->error =
mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context);
if(instance->error == MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Auth success");
instance->auth_context.auth_success = true;
instance->mfu_event.data->auth_context = instance->auth_context;
instance->mfu_event.type = MfUltralightPollerEventTypeAuthSuccess;
command = instance->callback(instance->general_event, instance->context);
} else {
FURI_LOG_D(TAG, "Auth failed");
instance->auth_context.auth_success = false;
instance->mfu_event.type = MfUltralightPollerEventTypeAuthFailed;
command = instance->callback(instance->general_event, instance->context);
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
}
}
}
instance->state = MfUltralightPollerStateReadPages;
return command;
}
static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* instance) {
MfUltralightPageReadCommandData data = {};
uint16_t start_page = instance->pages_read;
if(MF_ULTRALIGHT_IS_NTAG_I2C(instance->data->type)) {
uint8_t tag = 0;
uint8_t sector = 0;
uint8_t pages_left = 0;
if(mf_ultralight_poller_ntag_i2c_addr_lin_to_tag(
instance, start_page, &sector, &tag, &pages_left)) {
instance->error =
mf_ultralight_poller_async_read_page_from_sector(instance, sector, tag, &data);
} else {
FURI_LOG_D(TAG, "Failed to calculate sector and tag from %d page", start_page);
instance->error = MfUltralightErrorProtocol;
}
} else {
instance->error = mf_ultralight_poller_async_read_page(instance, start_page, &data);
}
if(instance->error == MfUltralightErrorNone) {
for(size_t i = 0; i < 4; i++) {
if(start_page + i < instance->pages_total) {
FURI_LOG_D(TAG, "Read page %d success", start_page + i);
instance->data->page[start_page + i] = data.page[i];
instance->pages_read++;
instance->data->pages_read = instance->pages_read;
}
}
if(instance->pages_read == instance->pages_total) {
instance->state = MfUltralightPollerStateReadCounters;
}
} else {
FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
if(instance->pages_read) {
instance->state = MfUltralightPollerStateReadCounters;
} else {
instance->state = MfUltralightPollerStateReadFailed;
}
}
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoller* instance) {
do {
if(!mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportPasswordAuth))
break;
MfUltralightConfigPages* config = NULL;
mf_ultralight_get_config_page(instance->data, &config);
if(instance->auth_context.auth_success) {
config->password = instance->auth_context.password;
config->pack = instance->auth_context.pack;
} else if(config->access.authlim == 0) {
FURI_LOG_D(TAG, "No limits in authentication. Trying default password");
nfc_util_num2bytes(
MF_ULTRALIGHT_DEFAULT_PASSWORD,
sizeof(MfUltralightAuthPassword),
instance->auth_context.password.data);
instance->error =
mf_ultralight_poller_async_auth_pwd(instance, &instance->auth_context);
if(instance->error == MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Default password detected");
nfc_util_num2bytes(
MF_ULTRALIGHT_DEFAULT_PASSWORD,
sizeof(MfUltralightAuthPassword),
config->password.data);
config->pack = instance->auth_context.pack;
}
}
if(instance->pages_read != instance->pages_total) {
// Probably password protected, fix AUTH0 and PROT so before AUTH0
// can be written and since AUTH0 won't be readable, like on the
// original card
config->auth0 = instance->pages_read;
config->access.prot = true;
}
} while(false);
instance->state = MfUltralightPollerStateReadSuccess;
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) {
FURI_LOG_D(TAG, "Read Failed");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
instance->mfu_event.data->error = instance->error;
NfcCommand command = instance->callback(instance->general_event, instance->context);
instance->state = MfUltralightPollerStateIdle;
return command;
}
static NfcCommand mf_ultralight_poller_handler_read_success(MfUltralightPoller* instance) {
FURI_LOG_D(TAG, "Read success");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
instance->mfu_event.type = MfUltralightPollerEventTypeReadSuccess;
NfcCommand command = instance->callback(instance->general_event, instance->context);
return command;
}
static const MfUltralightPollerReadHandler
mf_ultralight_poller_read_handler[MfUltralightPollerStateNum] = {
[MfUltralightPollerStateIdle] = mf_ultralight_poller_handler_idle,
[MfUltralightPollerStateReadVersion] = mf_ultralight_poller_handler_read_version,
[MfUltralightPollerStateDetectMfulC] = mf_ultralight_poller_handler_check_ultralight_c,
[MfUltralightPollerStateDetectNtag203] = mf_ultralight_poller_handler_check_ntag_203,
[MfUltralightPollerStateGetFeatureSet] = mf_ultralight_poller_handler_get_feature_set,
[MfUltralightPollerStateReadSignature] = mf_ultralight_poller_handler_read_signature,
[MfUltralightPollerStateReadCounters] = mf_ultralight_poller_handler_read_counters,
[MfUltralightPollerStateReadTearingFlags] =
mf_ultralight_poller_handler_read_tearing_flags,
[MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth,
[MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass,
[MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages,
[MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail,
[MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success,
};
static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.event_data);
furi_assert(event.protocol == NfcProtocolIso14443_3a);
MfUltralightPoller* instance = context;
furi_assert(instance->callback);
const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
command = mf_ultralight_poller_read_handler[instance->state](instance);
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
instance->mfu_event.type = MfUltralightPollerEventTypeReadFailed;
command = instance->callback(instance->general_event, instance->context);
}
return command;
}
static bool mf_ultralight_poller_detect(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.event_data);
furi_assert(event.protocol == NfcProtocolIso14443_3a);
bool protocol_detected = false;
MfUltralightPoller* instance = context;
const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
MfUltralightPageReadCommandData read_page_cmd_data = {};
MfUltralightError error =
mf_ultralight_poller_async_read_page(instance, 0, &read_page_cmd_data);
protocol_detected = (error == MfUltralightErrorNone);
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
}
return protocol_detected;
}
const NfcPollerBase mf_ultralight_poller = {
.alloc = (NfcPollerAlloc)mf_ultralight_poller_alloc,
.free = (NfcPollerFree)mf_ultralight_poller_free,
.set_callback = (NfcPollerSetCallback)mf_ultralight_poller_set_callback,
.run = (NfcPollerRun)mf_ultralight_poller_run,
.detect = (NfcPollerDetect)mf_ultralight_poller_detect,
.get_data = (NfcPollerGetData)mf_ultralight_poller_get_data,
};
@@ -0,0 +1,41 @@
#pragma once
#include "mf_ultralight.h"
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct MfUltralightPoller MfUltralightPoller;
typedef enum {
MfUltralightPollerEventTypeAuthRequest,
MfUltralightPollerEventTypeAuthSuccess,
MfUltralightPollerEventTypeAuthFailed,
MfUltralightPollerEventTypeReadSuccess,
MfUltralightPollerEventTypeReadFailed,
} MfUltralightPollerEventType;
typedef struct {
MfUltralightAuthPassword password;
MfUltralightAuthPack pack;
bool auth_success;
bool skip_auth;
} MfUltralightPollerAuthContext;
typedef struct {
union {
MfUltralightPollerAuthContext auth_context;
MfUltralightError error;
};
} MfUltralightPollerEventData;
typedef struct {
MfUltralightPollerEventType type;
MfUltralightPollerEventData* data;
} MfUltralightPollerEvent;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,5 @@
#pragma once
#include <nfc/protocols/nfc_poller_base.h>
extern const NfcPollerBase mf_ultralight_poller;
@@ -0,0 +1,308 @@
#include "mf_ultralight_poller_i.h"
#include <furi.h>
#define TAG "MfUltralightPoller"
MfUltralightError mf_ultralight_process_error(Iso14443_3aError error) {
MfUltralightError ret = MfUltralightErrorNone;
switch(error) {
case Iso14443_3aErrorNone:
ret = MfUltralightErrorNone;
break;
case Iso14443_3aErrorNotPresent:
ret = MfUltralightErrorNotPresent;
break;
case Iso14443_3aErrorColResFailed:
case Iso14443_3aErrorCommunication:
case Iso14443_3aErrorWrongCrc:
ret = MfUltralightErrorProtocol;
break;
case Iso14443_3aErrorTimeout:
ret = MfUltralightErrorTimeout;
break;
default:
ret = MfUltralightErrorProtocol;
break;
}
return ret;
}
MfUltralightError mf_ultralight_poller_async_auth_pwd(
MfUltralightPoller* instance,
MfUltralightPollerAuthContext* data) {
uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009
memccpy(&auth_cmd[1], data->password.data, 0, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE);
bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd));
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_PACK_SIZE) {
ret = MfUltralightErrorAuth;
break;
}
bit_buffer_write_bytes(instance->rx_buffer, data->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE);
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance) {
uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00};
bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd));
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if((bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_RESPONSE_SIZE) &&
(bit_buffer_get_byte(instance->rx_buffer, 0) != 0xAF)) {
ret = MfUltralightErrorAuth;
break;
}
//Save encrypted PICC random number RndB here if needed
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_read_page_from_sector(
MfUltralightPoller* instance,
uint8_t sector,
uint8_t tag,
MfUltralightPageReadCommandData* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
const uint8_t select_sector_cmd[2] = {MF_ULTRALIGHT_CMD_SECTOR_SELECT, 0xff};
bit_buffer_copy_bytes(instance->tx_buffer, select_sector_cmd, sizeof(select_sector_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorWrongCrc) {
FURI_LOG_D(TAG, "Failed to issue sector select command");
ret = mf_ultralight_process_error(error);
break;
}
const uint8_t read_sector_cmd[4] = {sector, 0x00, 0x00, 0x00};
bit_buffer_copy_bytes(instance->tx_buffer, read_sector_cmd, sizeof(read_sector_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorTimeout) {
// This is NOT a typo! The tag ACKs by not sending a response within 1ms.
FURI_LOG_D(TAG, "Sector %u select NAK'd", sector);
ret = MfUltralightErrorProtocol;
break;
}
ret = mf_ultralight_poller_async_read_page(instance, tag, data);
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_read_page(
MfUltralightPoller* instance,
uint8_t start_page,
MfUltralightPageReadCommandData* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
uint8_t read_page_cmd[2] = {MF_ULTRALIGHT_CMD_READ_PAGE, start_page};
bit_buffer_copy_bytes(instance->tx_buffer, read_page_cmd, sizeof(read_page_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) !=
sizeof(MfUltralightPageReadCommandData)) {
ret = MfUltralightErrorProtocol;
break;
}
bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightPageReadCommandData));
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_write_page(
MfUltralightPoller* instance,
uint8_t page,
MfUltralightPage* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
uint8_t write_page_cmd[MF_ULTRALIGHT_PAGE_SIZE + 2] = {MF_ULTRALIGHT_CMD_WRITE_PAGE, page};
memcpy(&write_page_cmd[2], data->data, MF_ULTRALIGHT_PAGE_SIZE);
bit_buffer_copy_bytes(instance->tx_buffer, write_page_cmd, sizeof(write_page_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorWrongCrc) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size(instance->rx_buffer) != 4) {
ret = MfUltralightErrorProtocol;
break;
}
if(!bit_buffer_starts_with_byte(instance->rx_buffer, MF_ULTRALIGHT_CMD_ACK)) {
ret = MfUltralightErrorProtocol;
break;
}
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_read_version(
MfUltralightPoller* instance,
MfUltralightVersion* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
const uint8_t get_version_cmd = MF_ULTRALIGHT_CMD_GET_VERSION;
bit_buffer_copy_bytes(instance->tx_buffer, &get_version_cmd, sizeof(get_version_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightVersion)) {
FURI_LOG_I(
TAG, "Read Version failed: %zu", bit_buffer_get_size_bytes(instance->rx_buffer));
ret = MfUltralightErrorProtocol;
break;
}
bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightVersion));
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_read_signature(
MfUltralightPoller* instance,
MfUltralightSignature* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
const uint8_t read_signature_cmd[2] = {MF_ULTRALIGHT_CMD_READ_SIG, 0x00};
bit_buffer_copy_bytes(instance->tx_buffer, read_signature_cmd, sizeof(read_signature_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightSignature)) {
ret = MfUltralightErrorProtocol;
break;
}
bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightSignature));
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_read_counter(
MfUltralightPoller* instance,
uint8_t counter_num,
MfUltralightCounter* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
uint8_t read_counter_cmd[2] = {MF_ULTRALIGHT_CMD_READ_CNT, counter_num};
bit_buffer_copy_bytes(instance->tx_buffer, read_counter_cmd, sizeof(read_counter_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_COUNTER_SIZE) {
ret = MfUltralightErrorProtocol;
break;
}
bit_buffer_write_bytes(instance->rx_buffer, data->data, MF_ULTRALIGHT_COUNTER_SIZE);
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_async_read_tearing_flag(
MfUltralightPoller* instance,
uint8_t tearing_falg_num,
MfUltralightTearingFlag* data) {
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
do {
uint8_t check_tearing_cmd[2] = {MF_ULTRALIGHT_CMD_CHECK_TEARING, tearing_falg_num};
bit_buffer_copy_bytes(instance->tx_buffer, check_tearing_cmd, sizeof(check_tearing_cmd));
error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC);
if(error != Iso14443_3aErrorNone) {
ret = mf_ultralight_process_error(error);
break;
}
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(MfUltralightTearingFlag)) {
ret = MfUltralightErrorProtocol;
break;
}
bit_buffer_write_bytes(instance->rx_buffer, data, sizeof(MfUltralightTearingFlag));
} while(false);
return ret;
}
@@ -0,0 +1,148 @@
#pragma once
#include "mf_ultralight_poller.h"
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h>
#include <lib/nfc/helpers/nfc_util.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000)
#define MF_ULTRALIGHT_MAX_BUFF_SIZE (64)
#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL)
#define MF_ULTRALIGHT_IS_NTAG_I2C(type) \
(((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \
((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K))
typedef struct {
MfUltralightPage page;
uint8_t page_to_write;
} MfUltralightPollerWritePageCommand;
typedef struct {
MfUltralightPageReadCommandData data;
uint8_t start_page;
} MfUltralightPollerReadPageCommand;
typedef struct {
MfUltralightCounter data;
uint8_t counter_num;
} MfUltralightPollerReadCounterCommand;
typedef struct {
MfUltralightTearingFlag data;
uint8_t tearing_flag_num;
} MfUltralightPollerReadTearingFlagCommand;
typedef union {
MfUltralightPollerWritePageCommand write_cmd;
MfUltralightPollerReadPageCommand read_cmd;
MfUltralightVersion version;
MfUltralightSignature signature;
MfUltralightPollerReadCounterCommand counter_cmd;
MfUltralightPollerReadTearingFlagCommand tearing_flag_cmd;
MfUltralightData* data;
} MfUltralightPollerContextData;
typedef enum {
MfUltralightPollerStateIdle,
MfUltralightPollerStateReadVersion,
MfUltralightPollerStateDetectMfulC,
MfUltralightPollerStateDetectNtag203,
MfUltralightPollerStateGetFeatureSet,
MfUltralightPollerStateReadSignature,
MfUltralightPollerStateReadCounters,
MfUltralightPollerStateReadTearingFlags,
MfUltralightPollerStateAuth,
MfUltralightPollerStateReadPages,
MfUltralightPollerStateTryDefaultPass,
MfUltralightPollerStateReadFailed,
MfUltralightPollerStateReadSuccess,
MfUltralightPollerStateNum,
} MfUltralightPollerState;
struct MfUltralightPoller {
Iso14443_3aPoller* iso14443_3a_poller;
MfUltralightPollerState state;
BitBuffer* tx_buffer;
BitBuffer* rx_buffer;
MfUltralightData* data;
MfUltralightPollerAuthContext auth_context;
uint32_t feature_set;
uint16_t pages_read;
uint16_t pages_total;
uint8_t counters_read;
uint8_t counters_total;
uint8_t tearing_flag_read;
uint8_t tearing_flag_total;
MfUltralightError error;
NfcGenericEvent general_event;
MfUltralightPollerEvent mfu_event;
MfUltralightPollerEventData mfu_event_data;
NfcGenericCallback callback;
void* context;
};
MfUltralightError mf_ultralight_process_error(Iso14443_3aError error);
MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller);
void mf_ultralight_poller_free(MfUltralightPoller* instance);
const MfUltralightData* mf_ultralight_poller_get_data(MfUltralightPoller* instance);
bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag(
MfUltralightPoller* instance,
uint16_t lin_addr,
uint8_t* sector,
uint8_t* tag,
uint8_t* pages_left);
MfUltralightError mf_ultralight_poller_async_auth_pwd(
MfUltralightPoller* instance,
MfUltralightPollerAuthContext* data);
MfUltralightError mf_ultralight_poller_async_authenticate(MfUltralightPoller* instance);
MfUltralightError mf_ultralight_poller_async_read_page(
MfUltralightPoller* instance,
uint8_t start_page,
MfUltralightPageReadCommandData* data);
MfUltralightError mf_ultralight_poller_async_read_page_from_sector(
MfUltralightPoller* instance,
uint8_t sector,
uint8_t tag,
MfUltralightPageReadCommandData* data);
MfUltralightError mf_ultralight_poller_async_write_page(
MfUltralightPoller* instance,
uint8_t page,
MfUltralightPage* data);
MfUltralightError mf_ultralight_poller_async_read_version(
MfUltralightPoller* instance,
MfUltralightVersion* data);
MfUltralightError mf_ultralight_poller_async_read_signature(
MfUltralightPoller* instance,
MfUltralightSignature* data);
MfUltralightError mf_ultralight_poller_async_read_counter(
MfUltralightPoller* instance,
uint8_t counter_num,
MfUltralightCounter* data);
MfUltralightError mf_ultralight_poller_async_read_tearing_flag(
MfUltralightPoller* instance,
uint8_t tearing_falg_num,
MfUltralightTearingFlag* data);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,287 @@
#include "mf_ultralight_poller_i.h"
#include <nfc/nfc_poller.h>
#include <furi.h>
#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0)
typedef enum {
MfUltralightPollerCmdTypeReadPage,
MfUltralightPollerCmdTypeWritePage,
MfUltralightPollerCmdTypeReadVersion,
MfUltralightPollerCmdTypeReadSignature,
MfUltralightPollerCmdTypeReadCounter,
MfUltralightPollerCmdTypeReadTearingFlag,
MfUltralightPollerCmdTypeNum,
} MfUltralightPollerCmdType;
typedef struct {
MfUltralightPollerCmdType cmd_type;
FuriThreadId thread_id;
MfUltralightError error;
MfUltralightPollerContextData data;
} MfUltralightPollerContext;
typedef MfUltralightError (*MfUltralightPollerCmdHandler)(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data);
MfUltralightError mf_ultralight_poller_read_page_handler(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data) {
return mf_ultralight_poller_async_read_page(
poller, data->read_cmd.start_page, &data->read_cmd.data);
}
MfUltralightError mf_ultralight_poller_write_page_handler(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data) {
return mf_ultralight_poller_async_write_page(
poller, data->write_cmd.page_to_write, &data->write_cmd.page);
}
MfUltralightError mf_ultralight_poller_read_version_handler(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data) {
return mf_ultralight_poller_async_read_version(poller, &data->version);
}
MfUltralightError mf_ultralight_poller_read_signature_handler(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data) {
return mf_ultralight_poller_async_read_signature(poller, &data->signature);
}
MfUltralightError mf_ultralight_poller_read_counter_handler(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data) {
return mf_ultralight_poller_async_read_counter(
poller, data->counter_cmd.counter_num, &data->counter_cmd.data);
}
MfUltralightError mf_ultralight_poller_read_tearing_flag_handler(
MfUltralightPoller* poller,
MfUltralightPollerContextData* data) {
return mf_ultralight_poller_async_read_tearing_flag(
poller, data->tearing_flag_cmd.tearing_flag_num, &data->tearing_flag_cmd.data);
}
static const MfUltralightPollerCmdHandler
mf_ultralight_poller_cmd_handlers[MfUltralightPollerCmdTypeNum] = {
[MfUltralightPollerCmdTypeReadPage] = mf_ultralight_poller_read_page_handler,
[MfUltralightPollerCmdTypeWritePage] = mf_ultralight_poller_write_page_handler,
[MfUltralightPollerCmdTypeReadVersion] = mf_ultralight_poller_read_version_handler,
[MfUltralightPollerCmdTypeReadSignature] = mf_ultralight_poller_read_signature_handler,
[MfUltralightPollerCmdTypeReadCounter] = mf_ultralight_poller_read_counter_handler,
[MfUltralightPollerCmdTypeReadTearingFlag] =
mf_ultralight_poller_read_tearing_flag_handler,
};
static NfcCommand mf_ultralight_poller_cmd_callback(NfcGenericEvent event, void* context) {
furi_assert(event.instance);
furi_assert(event.protocol == NfcProtocolIso14443_3a);
furi_assert(event.event_data);
furi_assert(context);
MfUltralightPollerContext* poller_context = context;
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
Iso14443_3aPoller* iso14443_3a_poller = event.instance;
MfUltralightPoller* mfu_poller = mf_ultralight_poller_alloc(iso14443_3a_poller);
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
poller_context->error = mf_ultralight_poller_cmd_handlers[poller_context->cmd_type](
mfu_poller, &poller_context->data);
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
poller_context->error = mf_ultralight_process_error(iso14443_3a_event->data->error);
}
furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT);
mf_ultralight_poller_free(mfu_poller);
return NfcCommandStop;
}
static MfUltralightError
mf_ultralight_poller_cmd_execute(Nfc* nfc, MfUltralightPollerContext* poller_ctx) {
furi_assert(poller_ctx->cmd_type < MfUltralightPollerCmdTypeNum);
poller_ctx->thread_id = furi_thread_get_current_id();
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
nfc_poller_start(poller, mf_ultralight_poller_cmd_callback, poller_ctx);
furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever);
furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT);
nfc_poller_stop(poller);
nfc_poller_free(poller);
return poller_ctx->error;
}
MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {
.cmd_type = MfUltralightPollerCmdTypeReadPage,
.data.read_cmd.start_page = page,
};
MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context);
if(error == MfUltralightErrorNone) {
*data = poller_context.data.read_cmd.data.page[0];
}
return error;
}
MfUltralightError
mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {
.cmd_type = MfUltralightPollerCmdTypeWritePage,
.data.write_cmd =
{
.page_to_write = page,
.page = *data,
},
};
MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context);
return error;
}
MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {
.cmd_type = MfUltralightPollerCmdTypeReadVersion,
};
MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context);
if(error == MfUltralightErrorNone) {
*data = poller_context.data.version;
}
return error;
}
MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {
.cmd_type = MfUltralightPollerCmdTypeReadSignature,
};
MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context);
if(error == MfUltralightErrorNone) {
*data = poller_context.data.signature;
}
return error;
}
MfUltralightError
mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {
.cmd_type = MfUltralightPollerCmdTypeReadCounter,
.data.counter_cmd.counter_num = counter_num,
};
MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context);
if(error == MfUltralightErrorNone) {
*data = poller_context.data.counter_cmd.data;
}
return error;
}
MfUltralightError mf_ultralight_poller_read_tearing_flag(
Nfc* nfc,
uint8_t flag_num,
MfUltralightTearingFlag* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {
.cmd_type = MfUltralightPollerCmdTypeReadTearingFlag,
.data.tearing_flag_cmd.tearing_flag_num = flag_num,
};
MfUltralightError error = mf_ultralight_poller_cmd_execute(nfc, &poller_context);
if(error == MfUltralightErrorNone) {
*data = poller_context.data.tearing_flag_cmd.data;
}
return error;
}
static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.instance);
furi_assert(event.event_data);
furi_assert(event.protocol == NfcProtocolMfUltralight);
NfcCommand command = NfcCommandContinue;
MfUltralightPollerContext* poller_context = context;
MfUltralightPoller* mfu_poller = event.instance;
MfUltralightPollerEvent* mfu_event = event.event_data;
if(mfu_event->type == MfUltralightPollerEventTypeReadSuccess) {
mf_ultralight_copy(poller_context->data.data, mf_ultralight_poller_get_data(mfu_poller));
poller_context->error = MfUltralightErrorNone;
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) {
poller_context->error = mfu_event->data->error;
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
mfu_event->data->auth_context.skip_auth = true;
}
if(command == NfcCommandStop) {
furi_thread_flags_set(poller_context->thread_id, MF_ULTRALIGHT_POLLER_COMPLETE_EVENT);
}
return command;
}
MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data) {
furi_assert(nfc);
furi_assert(data);
MfUltralightPollerContext poller_context = {};
poller_context.thread_id = furi_thread_get_current_id();
poller_context.data.data = mf_ultralight_alloc();
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight);
nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context);
furi_thread_flags_wait(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever);
furi_thread_flags_clear(MF_ULTRALIGHT_POLLER_COMPLETE_EVENT);
nfc_poller_stop(poller);
nfc_poller_free(poller);
if(poller_context.error == MfUltralightErrorNone) {
mf_ultralight_copy(data, poller_context.data.data);
}
mf_ultralight_free(poller_context.data.data);
return poller_context.error;
}
@@ -0,0 +1,30 @@
#pragma once
#include "mf_ultralight.h"
#include <nfc/nfc.h>
#ifdef __cplusplus
extern "C" {
#endif
MfUltralightError mf_ultralight_poller_read_page(Nfc* nfc, uint16_t page, MfUltralightPage* data);
MfUltralightError mf_ultralight_poller_write_page(Nfc* nfc, uint16_t page, MfUltralightPage* data);
MfUltralightError mf_ultralight_poller_read_version(Nfc* nfc, MfUltralightVersion* data);
MfUltralightError mf_ultralight_poller_read_signature(Nfc* nfc, MfUltralightSignature* data);
MfUltralightError
mf_ultralight_poller_read_counter(Nfc* nfc, uint8_t counter_num, MfUltralightCounter* data);
MfUltralightError mf_ultralight_poller_read_tearing_flag(
Nfc* nfc,
uint8_t flag_num,
MfUltralightTearingFlag* data);
MfUltralightError mf_ultralight_poller_read_card(Nfc* nfc, MfUltralightData* data);
#ifdef __cplusplus
}
#endif