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:
@@ -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, §or, &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
|
||||
Reference in New Issue
Block a user