From 893ff5734e2b2cd56db1f22e5dba475dc1325b72 Mon Sep 17 00:00:00 2001 From: gsp8181 <3165098+gsp8181@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:53:11 +0000 Subject: [PATCH 1/4] Create ITSO parser Standard for UK transport cards outside of Oyster --- .../main/nfc/plugins/supported_cards/itso.c | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/itso.c diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c new file mode 100644 index 00000000000..8cfe99363c4 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -0,0 +1,157 @@ +/* itso.c - Parser for ITSO cards (United Kingdom). */ +#include "nfc_supported_card_plugin.h" + +#include +#include + +#include +#include + +#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[17]; + + 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, ×tamp); + FuriString* timestamp_str = furi_string_alloc(); + locale_format_date(timestamp_str, ×tamp, 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; +} From 67445c9a6c4d9d080e0881215a3f9a1b0d85fc5e Mon Sep 17 00:00:00 2001 From: gsp8181 <3165098+gsp8181@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:53:59 +0000 Subject: [PATCH 2/4] Update application.fam with ITSO parser --- applications/main/nfc/application.fam | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index b92d0ebf1ab..06a1f1f127c 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -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"], From 45a8840093fa7350c62146597c4753044e0d8180 Mon Sep 17 00:00:00 2001 From: gsp8181 <3165098+gsp8181@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:47:29 +0000 Subject: [PATCH 3/4] Update date buffer to fix access outside array --- applications/main/nfc/plugins/supported_cards/itso.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c index 8cfe99363c4..dd7d3defb3e 100644 --- a/applications/main/nfc/plugins/supported_cards/itso.c +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -90,7 +90,7 @@ static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { uint64_t x2 = swap_uint64(itso_file->part2); char cardBuff[32]; - char dateBuff[17]; + char dateBuff[18]; snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2); snprintf(dateBuff, sizeof(dateBuff), "%llx", x2); From 1b519fe29d4987cbd194c39a9739f810bb486499 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 6 Feb 2024 18:39:09 +0000 Subject: [PATCH 4/4] nfc app: use rtc function in itso plugin --- .../main/nfc/plugins/supported_cards/itso.c | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c index dd7d3defb3e..a4e0c8869a4 100644 --- a/applications/main/nfc/plugins/supported_cards/itso.c +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -7,13 +7,6 @@ #include #include -#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; @@ -29,32 +22,6 @@ uint64_t swap_uint64(uint64_t val) { 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); @@ -121,7 +88,7 @@ static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { } FuriHalRtcDateTime timestamp = {0}; - timestamp_to_datetime(unixTimestamp, ×tamp); + furi_hal_rtc_timestamp_to_datetime(unixTimestamp, ×tamp); FuriString* timestamp_str = furi_string_alloc(); locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-");