Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions applications/main/nfc/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ App(
sources=["plugins/supported_cards/hid.c"],
)

App(
appid="itso_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="itso_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/itso.c"],
)

App(
appid="nfc_start",
targets=["f7"],
Expand Down
157 changes: 157 additions & 0 deletions applications/main/nfc/plugins/supported_cards/itso.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/* itso.c - Parser for ITSO cards (United Kingdom). */
#include "nfc_supported_card_plugin.h"

#include <flipper_application/flipper_application.h>
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>

#include <applications/services/locale/locale.h>
#include <furi_hal_rtc.h>

#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60
#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60)
#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24)
#define FURI_HAL_RTC_EPOCH_START_YEAR 1970
#define FURI_HAL_RTC_IS_LEAP_YEAR(year) \
((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0))

static const MfDesfireApplicationId itso_app_id = {.data = {0x16, 0x02, 0xa0}};
static const MfDesfireFileId itso_file_id = 0x0f;

int64_t swap_int64(int64_t val) {
val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
}

uint64_t swap_uint64(uint64_t val) {
val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
return (val << 32) | (val >> 32);
}

void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) {
uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY;
uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY;

datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR;

while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) {
days -= furi_hal_rtc_get_days_per_year(datetime->year);
(datetime->year)++;
}

datetime->month = 1;
while(days >= furi_hal_rtc_get_days_per_month(
FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month)) {
days -= furi_hal_rtc_get_days_per_month(
FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year), datetime->month);
(datetime->month)++;
}

datetime->day = days + 1;
datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR;
datetime->minute =
(seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE;
datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE;
}

static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
furi_assert(parsed_data);

bool parsed = false;

do {
const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);

const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_app_id);
if(app == NULL) break;

typedef struct {
uint64_t part1;
uint64_t part2;
uint64_t part3;
uint64_t part4;
} ItsoFile;

const MfDesfireFileSettings* file_settings =
mf_desfire_get_file_settings(app, &itso_file_id);

if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
file_settings->data.size < sizeof(ItsoFile))
break;

const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_file_id);
if(file_data == NULL) break;

const ItsoFile* itso_file = simple_array_cget_data(file_data->data);

uint64_t x1 = swap_uint64(itso_file->part1);
uint64_t x2 = swap_uint64(itso_file->part2);

char cardBuff[32];
char dateBuff[18];

snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2);
snprintf(dateBuff, sizeof(dateBuff), "%llx", x2);

char* cardp = cardBuff + 4;
cardp[18] = '\0';

// All itso card numbers are prefixed with "633597"
if(strncmp(cardp, "633597", 6) != 0) break;

char* datep = dateBuff + 12;
dateBuff[17] = '\0';

// DateStamp is defined in BS EN 1545 - Days passed since 01/01/1997
uint32_t dateStamp = (int)strtol(datep, NULL, 16);
uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U;

furi_string_set(parsed_data, "\e#ITSO Card\n");

// Digit count in each space-separated group
static const uint8_t digit_count[] = {6, 4, 4, 4};

for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) {
for(uint32_t j = 0; j < digit_count[i]; ++j) {
furi_string_push_back(parsed_data, cardp[j + k]);
}
furi_string_push_back(parsed_data, ' ');
}

FuriHalRtcDateTime timestamp = {0};
timestamp_to_datetime(unixTimestamp, &timestamp);
FuriString* timestamp_str = furi_string_alloc();
locale_format_date(timestamp_str, &timestamp, locale_get_date_format(), "-");

furi_string_cat(parsed_data, "\nExpiry: ");
furi_string_cat(parsed_data, timestamp_str);

furi_string_free(timestamp_str);

parsed = true;
} while(false);

return parsed;
}

/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin itso_plugin = {
.protocol = NfcProtocolMfDesfire,
.verify = NULL,
.read = NULL,
.parse = itso_parse,
};

/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor itso_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &itso_plugin,
};

/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* itso_plugin_ep() {
return &itso_plugin_descriptor;
}