"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.
		
			
				
	
	
		
			117 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* myki.c - Parser for myki cards (Melbourne, Australia).
 | 
						|
 *
 | 
						|
 * Based on the code by Emily Trau (https://github.com/emilytrau)
 | 
						|
 * Original pull request URL: https://github.com/flipperdevices/flipperzero-firmware/pull/2326
 | 
						|
 * Reference: https://github.com/metrodroid/metrodroid/wiki/Myki
 | 
						|
 */
 | 
						|
#include "nfc_supported_card_plugin.h"
 | 
						|
 | 
						|
#include <flipper_application/flipper_application.h>
 | 
						|
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
 | 
						|
 | 
						|
static const MfDesfireApplicationId myki_app_id = {.data = {0x00, 0x11, 0xf2}};
 | 
						|
static const MfDesfireFileId myki_file_id = 0x0f;
 | 
						|
 | 
						|
static uint8_t myki_calculate_luhn(uint64_t number) {
 | 
						|
    // https://en.wikipedia.org/wiki/Luhn_algorithm
 | 
						|
    // Drop existing check digit to form payload
 | 
						|
    uint64_t payload = number / 10;
 | 
						|
    int sum = 0;
 | 
						|
    int position = 0;
 | 
						|
 | 
						|
    while(payload > 0) {
 | 
						|
        int digit = payload % 10;
 | 
						|
        if(position % 2 == 0) {
 | 
						|
            digit *= 2;
 | 
						|
        }
 | 
						|
        if(digit > 9) {
 | 
						|
            digit = (digit / 10) + (digit % 10);
 | 
						|
        }
 | 
						|
        sum += digit;
 | 
						|
        payload /= 10;
 | 
						|
        position++;
 | 
						|
    }
 | 
						|
 | 
						|
    return (10 - (sum % 10)) % 10;
 | 
						|
}
 | 
						|
 | 
						|
static bool myki_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, &myki_app_id);
 | 
						|
        if(app == NULL) break;
 | 
						|
 | 
						|
        typedef struct {
 | 
						|
            uint32_t top;
 | 
						|
            uint32_t bottom;
 | 
						|
        } MykiFile;
 | 
						|
 | 
						|
        const MfDesfireFileSettings* file_settings =
 | 
						|
            mf_desfire_get_file_settings(app, &myki_file_id);
 | 
						|
 | 
						|
        if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
 | 
						|
           file_settings->data.size < sizeof(MykiFile))
 | 
						|
            break;
 | 
						|
 | 
						|
        const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_file_id);
 | 
						|
        if(file_data == NULL) break;
 | 
						|
 | 
						|
        const MykiFile* myki_file = simple_array_cget_data(file_data->data);
 | 
						|
 | 
						|
        // All myki card numbers are prefixed with "308425"
 | 
						|
        if(myki_file->top != 308425UL) break;
 | 
						|
        // Card numbers are always 15 digits in length
 | 
						|
        if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) break;
 | 
						|
 | 
						|
        uint64_t card_number = myki_file->top * 1000000000ULL + myki_file->bottom * 10UL;
 | 
						|
        // Stored card number doesn't include check digit
 | 
						|
        card_number += myki_calculate_luhn(card_number);
 | 
						|
 | 
						|
        furi_string_set(parsed_data, "\e#myki\n");
 | 
						|
 | 
						|
        // Stylise card number according to the physical card
 | 
						|
        char card_string[20];
 | 
						|
        snprintf(card_string, sizeof(card_string), "%llu", card_number);
 | 
						|
 | 
						|
        // Digit count in each space-separated group
 | 
						|
        static const uint8_t digit_count[] = {1, 5, 4, 4, 1};
 | 
						|
 | 
						|
        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, card_string[j + k]);
 | 
						|
            }
 | 
						|
            furi_string_push_back(parsed_data, ' ');
 | 
						|
        }
 | 
						|
 | 
						|
        parsed = true;
 | 
						|
    } while(false);
 | 
						|
 | 
						|
    return parsed;
 | 
						|
}
 | 
						|
 | 
						|
/* Actual implementation of app<>plugin interface */
 | 
						|
static const NfcSupportedCardsPlugin myki_plugin = {
 | 
						|
    .protocol = NfcProtocolMfDesfire,
 | 
						|
    .verify = NULL,
 | 
						|
    .read = NULL,
 | 
						|
    .parse = myki_parse,
 | 
						|
};
 | 
						|
 | 
						|
/* Plugin descriptor to comply with basic plugin specification */
 | 
						|
static const FlipperAppPluginDescriptor myki_plugin_descriptor = {
 | 
						|
    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
						|
    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
						|
    .entry_point = &myki_plugin,
 | 
						|
};
 | 
						|
 | 
						|
/* Plugin entry point - must return a pointer to const descriptor  */
 | 
						|
const FlipperAppPluginDescriptor* myki_plugin_ep() {
 | 
						|
    return &myki_plugin_descriptor;
 | 
						|
}
 |