Skip to content

Commit

Permalink
Implement support for reading Opal card (Sydney, Australia) (flipperd…
Browse files Browse the repository at this point in the history
…evices#2683)

* Implement support for reading Opal card (Sydney, Australia)
* stub_parser_verify_read: used UNUSED macro
* furi_hal_rtc: expose calendaring as functions
* opal: use bit-packed struct to parse, rather than manually shifting about
* Update f18 api symbols

Co-authored-by: あく <[email protected]>
  • Loading branch information
micolous and skotopes authored May 29, 2023
1 parent 66961da commit 363f555
Show file tree
Hide file tree
Showing 15 changed files with 356 additions and 37 deletions.
1 change: 1 addition & 0 deletions applications/main/nfc/scenes/nfc_scene_device_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ void nfc_scene_device_info_on_enter(void* context) {
}
} else if(
dev_data->protocol == NfcDeviceProtocolMifareClassic ||
dev_data->protocol == NfcDeviceProtocolMifareDesfire ||
dev_data->protocol == NfcDeviceProtocolMifareUl) {
furi_string_set(temp_str, nfc->dev->dev_data.parsed_data);
}
Expand Down
59 changes: 32 additions & 27 deletions applications/main/nfc/scenes/nfc_scene_mf_desfire_read_success.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,40 @@ void nfc_scene_mf_desfire_read_success_on_enter(void* context) {
Widget* widget = nfc->widget;

// Prepare string for data display
FuriString* temp_str = furi_string_alloc_printf("\e#MIFARE DESfire\n");
furi_string_cat_printf(temp_str, "UID:");
for(size_t i = 0; i < nfc_data->uid_len; i++) {
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
}
FuriString* temp_str = NULL;
if(furi_string_size(nfc->dev->dev_data.parsed_data)) {
temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data);
} else {
temp_str = furi_string_alloc_printf("\e#MIFARE DESFire\n");
furi_string_cat_printf(temp_str, "UID:");
for(size_t i = 0; i < nfc_data->uid_len; i++) {
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
}

uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1);
uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0;
furi_string_cat_printf(temp_str, "\n%lu", bytes_total);
if(data->version.sw_storage & 1) {
furi_string_push_back(temp_str, '+');
}
furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free);

uint16_t n_apps = 0;
uint16_t n_files = 0;
for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
n_apps++;
for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
n_files++;
uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1);
uint32_t bytes_free = data->free_memory ? data->free_memory->bytes : 0;
furi_string_cat_printf(temp_str, "\n%lu", bytes_total);
if(data->version.sw_storage & 1) {
furi_string_push_back(temp_str, '+');
}
furi_string_cat_printf(temp_str, " bytes, %lu bytes free\n", bytes_free);

uint16_t n_apps = 0;
uint16_t n_files = 0;
for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
n_apps++;
for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
n_files++;
}
}
furi_string_cat_printf(temp_str, "%d Application", n_apps);
if(n_apps != 1) {
furi_string_push_back(temp_str, 's');
}
furi_string_cat_printf(temp_str, ", %d file", n_files);
if(n_files != 1) {
furi_string_push_back(temp_str, 's');
}
}
furi_string_cat_printf(temp_str, "%d Application", n_apps);
if(n_apps != 1) {
furi_string_push_back(temp_str, 's');
}
furi_string_cat_printf(temp_str, ", %d file", n_files);
if(n_files != 1) {
furi_string_push_back(temp_str, 's');
}

notification_message_block(nfc->notifications, &sequence_set_green_255);
Expand Down
2 changes: 1 addition & 1 deletion applications/main/nfc/scenes/nfc_scene_nfc_data_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type));
} else if(protocol == NfcDeviceProtocolMifareDesfire) {
furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n");
furi_string_cat_printf(temp_str, "\e#MIFARE DESFire\n");
} else {
furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n");
}
Expand Down
1 change: 1 addition & 0 deletions applications/main/nfc/scenes/nfc_scene_saved_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
application_info_present = true;
} else if(
dev_data->protocol == NfcDeviceProtocolMifareClassic ||
dev_data->protocol == NfcDeviceProtocolMifareDesfire ||
dev_data->protocol == NfcDeviceProtocolMifareUl) {
application_info_present = nfc_supported_card_verify_and_parse(dev_data);
}
Expand Down
5 changes: 4 additions & 1 deletion firmware/targets/f18/api_symbols.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,27.0,,
Version,+,27.1,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
Expand Down Expand Up @@ -1048,6 +1048,8 @@ Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime*
Function,-,furi_hal_rtc_deinit_early,void,
Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode,
Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime*
Function,+,furi_hal_rtc_get_days_per_month,uint8_t,"_Bool, uint8_t"
Function,+,furi_hal_rtc_get_days_per_year,uint16_t,uint16_t
Function,+,furi_hal_rtc_get_fault_data,uint32_t,
Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode,
Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat,
Expand All @@ -1060,6 +1062,7 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t,
Function,-,furi_hal_rtc_init,void,
Function,-,furi_hal_rtc_init_early,void,
Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag
Function,+,furi_hal_rtc_is_leap_year,_Bool,uint16_t
Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag
Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode
Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime*
Expand Down
7 changes: 6 additions & 1 deletion firmware/targets/f7/api_symbols.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,27.0,,
Version,+,27.1,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
Expand Down Expand Up @@ -1313,6 +1313,8 @@ Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime*
Function,-,furi_hal_rtc_deinit_early,void,
Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode,
Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime*
Function,+,furi_hal_rtc_get_days_per_month,uint8_t,"_Bool, uint8_t"
Function,+,furi_hal_rtc_get_days_per_year,uint16_t,uint16_t
Function,+,furi_hal_rtc_get_fault_data,uint32_t,
Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode,
Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat,
Expand All @@ -1325,6 +1327,7 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t,
Function,-,furi_hal_rtc_init,void,
Function,-,furi_hal_rtc_init_early,void,
Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag
Function,+,furi_hal_rtc_is_leap_year,_Bool,uint16_t
Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag
Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode
Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime*
Expand Down Expand Up @@ -1991,6 +1994,8 @@ Function,-,mf_df_cat_key_settings,void,"MifareDesfireKeySettings*, FuriString*"
Function,-,mf_df_cat_version,void,"MifareDesfireVersion*, FuriString*"
Function,-,mf_df_check_card_type,_Bool,"uint8_t, uint8_t, uint8_t"
Function,-,mf_df_clear,void,MifareDesfireData*
Function,-,mf_df_get_application,MifareDesfireApplication*,"MifareDesfireData*, const uint8_t[3]*"
Function,-,mf_df_get_file,MifareDesfireFile*,"MifareDesfireApplication*, uint8_t"
Function,-,mf_df_parse_get_application_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireApplication**"
Function,-,mf_df_parse_get_file_ids_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile**"
Function,-,mf_df_parse_get_file_settings_response,_Bool,"uint8_t*, uint16_t, MifareDesfireFile*"
Expand Down
24 changes: 17 additions & 7 deletions firmware/targets/f7/furi_hal/furi_hal_rtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,8 @@ _Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch");
#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24)
#define FURI_HAL_RTC_MONTHS_COUNT 12
#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 uint8_t furi_hal_rtc_days_per_month[][FURI_HAL_RTC_MONTHS_COUNT] = {
static const uint8_t furi_hal_rtc_days_per_month[2][FURI_HAL_RTC_MONTHS_COUNT] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};

Expand Down Expand Up @@ -395,7 +393,7 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) {
uint8_t leap_years = 0;

for(uint16_t y = FURI_HAL_RTC_EPOCH_START_YEAR; y < datetime->year; y++) {
if(FURI_HAL_RTC_IS_LEAP_YEAR(y)) {
if(furi_hal_rtc_is_leap_year(y)) {
leap_years++;
} else {
years++;
Expand All @@ -406,10 +404,10 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) {
((years * furi_hal_rtc_days_per_year[0]) + (leap_years * furi_hal_rtc_days_per_year[1])) *
FURI_HAL_RTC_SECONDS_PER_DAY;

uint8_t year_index = (FURI_HAL_RTC_IS_LEAP_YEAR(datetime->year)) ? 1 : 0;
bool leap_year = furi_hal_rtc_is_leap_year(datetime->year);

for(uint8_t m = 0; m < (datetime->month - 1); m++) {
timestamp += furi_hal_rtc_days_per_month[year_index][m] * FURI_HAL_RTC_SECONDS_PER_DAY;
for(uint8_t m = 1; m < datetime->month; m++) {
timestamp += furi_hal_rtc_get_days_per_month(leap_year, m) * FURI_HAL_RTC_SECONDS_PER_DAY;
}

timestamp += (datetime->day - 1) * FURI_HAL_RTC_SECONDS_PER_DAY;
Expand All @@ -419,3 +417,15 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) {

return timestamp;
}

uint16_t furi_hal_rtc_get_days_per_year(uint16_t year) {
return furi_hal_rtc_days_per_year[furi_hal_rtc_is_leap_year(year) ? 1 : 0];
}

bool furi_hal_rtc_is_leap_year(uint16_t year) {
return (((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0);
}

uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month) {
return furi_hal_rtc_days_per_month[leap_year ? 1 : 0][month - 1];
}
24 changes: 24 additions & 0 deletions firmware/targets/furi_hal_include/furi_hal_rtc.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,30 @@ uint32_t furi_hal_rtc_get_timestamp();
*/
uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime);

/** Gets the number of days in the year according to the Gregorian calendar.
*
* @param year Input year.
*
* @return number of days in `year`.
*/
uint16_t furi_hal_rtc_get_days_per_year(uint16_t year);

/** Check if a year a leap year in the Gregorian calendar.
*
* @param year Input year.
*
* @return true if `year` is a leap year.
*/
bool furi_hal_rtc_is_leap_year(uint16_t year);

/** Get the number of days in the month.
*
* @param leap_year true to calculate based on leap years
* @param month month to check, where 1 = January
* @return the number of days in the month
*/
uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month);

#ifdef __cplusplus
}
#endif
13 changes: 13 additions & 0 deletions lib/nfc/nfc_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,19 @@ static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxCont
do {
if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
if(!mf_df_read_card(tx_rx, data)) break;
FURI_LOG_I(TAG, "Trying to parse a supported card ...");

// The model for parsing DESFire is a little different to other cards;
// we don't have parsers to provide encryption keys, so we can read the
// data normally, and then pass the read data to a parser.
//
// There are fully-protected DESFire cards, but providing keys for them
// is difficult (and unnessesary for many transit cards).
for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) {
if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareDesfire) {
if(nfc_supported_card[i].parse(nfc_worker->dev_data)) break;
}
}
read_success = true;
} while(false);

Expand Down
15 changes: 15 additions & 0 deletions lib/nfc/parsers/nfc_supported_card.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "troika_4k_parser.h"
#include "two_cities.h"
#include "all_in_one.h"
#include "opal.h"

NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
[NfcSupportedCardTypePlantain] =
Expand Down Expand Up @@ -50,6 +51,14 @@ NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
.read = all_in_one_parser_read,
.parse = all_in_one_parser_parse,
},
[NfcSupportedCardTypeOpal] =
{
.protocol = NfcDeviceProtocolMifareDesfire,
.verify = stub_parser_verify_read,
.read = stub_parser_verify_read,
.parse = opal_parser_parse,
},

};

bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) {
Expand All @@ -65,3 +74,9 @@ bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) {

return card_parsed;
}

bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
UNUSED(nfc_worker);
UNUSED(tx_rx);
return false;
}
6 changes: 6 additions & 0 deletions lib/nfc/parsers/nfc_supported_card.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ typedef enum {
NfcSupportedCardTypeTroika4K,
NfcSupportedCardTypeTwoCities,
NfcSupportedCardTypeAllInOne,
NfcSupportedCardTypeOpal,

NfcSupportedCardTypeEnd,
} NfcSupportedCardType;
Expand All @@ -31,3 +32,8 @@ typedef struct {
extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd];

bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data);

// stub_parser_verify_read does nothing, and always reports that it does not
// support the card. This is needed for DESFire card parsers which can't
// provide keys, and only use NfcSupportedCard->parse.
bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
Loading

0 comments on commit 363f555

Please sign in to comment.