Added social card moscow parser & troyka refactor

This commit is contained in:
assasinfil 2023-09-17 13:56:14 +03:00
parent 6a2adf69e6
commit ebde816105
17 changed files with 1899 additions and 164 deletions

View file

@ -1393,6 +1393,7 @@ Function,+,furi_hal_rtc_set_log_level,void,uint8_t
Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t
Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t"
Function,+,furi_hal_rtc_sync_shadow,void,
Function,+,furi_hal_rtc_timestamp_to_datetime,void,"uint32_t, FuriHalRtcDateTime*"
Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime*
Function,+,furi_hal_sd_get_card_state,FuriStatus,
Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo*

1 entry status name type params
1393 Function + furi_hal_rtc_set_pin_fails void uint32_t
1394 Function + furi_hal_rtc_set_register void FuriHalRtcRegister, uint32_t
1395 Function + furi_hal_rtc_sync_shadow void
1396 Function + furi_hal_rtc_timestamp_to_datetime void uint32_t, FuriHalRtcDateTime*
1397 Function + furi_hal_rtc_validate_datetime _Bool FuriHalRtcDateTime*
1398 Function + furi_hal_sd_get_card_state FuriStatus
1399 Function + furi_hal_sd_info FuriStatus FuriHalSdInfo*

View file

@ -429,3 +429,32 @@ bool furi_hal_rtc_is_leap_year(uint16_t year) {
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];
}
void furi_hal_rtc_timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) {
datetime->year = timestamp / (60 * 60 * 24 * 366) + FURI_HAL_RTC_EPOCH_START_YEAR;
uint16_t extra_days = (datetime->year - FURI_HAL_RTC_EPOCH_START_YEAR - 1) / 4;
uint16_t days_since_epoch = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY;
uint16_t days_since_epoch_without_extra_days = days_since_epoch - extra_days;
uint16_t days_in_this_year =
days_since_epoch_without_extra_days % furi_hal_rtc_days_per_year[0] + 1;
while(days_in_this_year > furi_hal_rtc_days_per_month[0][datetime->month]) {
days_in_this_year -= furi_hal_rtc_days_per_month[0][datetime->month];
datetime->month++;
}
datetime->month++;
if(furi_hal_rtc_is_leap_year(datetime->year)) {
datetime->day = days_in_this_year - 1;
} else {
datetime->day = days_in_this_year;
}
// uint16_t seconds_in_this_day =
// timestamp - (days_since_epoch * FURI_HAL_RTC_SECONDS_PER_DAY);
// datetime->hour = seconds_in_this_day / FURI_HAL_RTC_SECONDS_PER_HOUR;
// uint16_t minutes_in_this_day =
// seconds_in_this_day - (datetime->hour * FURI_HAL_RTC_SECONDS_PER_HOUR);
// datetime->minute = minutes_in_this_day / FURI_HAL_RTC_SECONDS_PER_MINUTE;
// datetime->second = minutes_in_this_day - (datetime->minute * FURI_HAL_RTC_SECONDS_PER_MINUTE);
datetime->second = timestamp % 60;
datetime->minute = timestamp / 60 % 60;
datetime->hour = timestamp / (60 * 60) % 24;
}

View file

@ -280,6 +280,13 @@ bool furi_hal_rtc_is_leap_year(uint16_t year);
*/
uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month);
/** Convert UNIX timestamp to DateTime
* @param timestamp The UNIX timestamp
*
* @param datetime The datetime
*/
void furi_hal_rtc_timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime);
#ifdef __cplusplus
}
#endif

398
lib/nfc/helpers/bit_lib.c Normal file
View file

@ -0,0 +1,398 @@
#include "bit_lib.h"
#include <core/check.h>
#include <stdio.h>
void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit) {
size_t last_index = data_size - 1;
for(size_t i = 0; i < last_index; ++i) {
data[i] = (data[i] << 1) | ((data[i + 1] >> 7) & 1);
}
data[last_index] = (data[last_index] << 1) | bit;
}
void bit_lib_set_bit(uint8_t* data, size_t position, bool bit) {
if(bit) {
data[position / 8] |= 1UL << (7 - (position % 8));
} else {
data[position / 8] &= ~(1UL << (7 - (position % 8)));
}
}
void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length) {
furi_check(length <= 8);
furi_check(length > 0);
for(uint8_t i = 0; i < length; ++i) {
uint8_t shift = (length - 1) - i;
bit_lib_set_bit(data, position + i, (byte >> shift) & 1); //-V610
}
}
bool bit_lib_get_bit(const uint8_t* data, size_t position) {
return (data[position / 8] >> (7 - (position % 8))) & 1;
}
uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length) {
uint8_t shift = position % 8;
if(shift == 0) {
return data[position / 8] >> (8 - length);
} else {
// TODO fix read out of bounds
uint8_t value = (data[position / 8] << (shift));
value |= data[position / 8 + 1] >> (8 - shift);
value = value >> (8 - length);
return value;
}
}
uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length) {
uint16_t value = 0;
if(length <= 8) {
value = bit_lib_get_bits(data, position, length);
} else {
value = bit_lib_get_bits(data, position, 8) << (length - 8);
value |= bit_lib_get_bits(data, position + 8, length - 8);
}
return value;
}
uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length) {
uint32_t value = 0;
if(length <= 8) {
value = bit_lib_get_bits(data, position, length);
} else if(length <= 16) {
value = bit_lib_get_bits(data, position, 8) << (length - 8);
value |= bit_lib_get_bits(data, position + 8, length - 8);
} else if(length <= 24) {
value = bit_lib_get_bits(data, position, 8) << (length - 8);
value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16);
value |= bit_lib_get_bits(data, position + 16, length - 16);
} else {
value = (uint32_t)bit_lib_get_bits(data, position, 8) << (length - 8);
value |= (uint32_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16);
value |= (uint32_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24);
value |= bit_lib_get_bits(data, position + 24, length - 24);
}
return value;
}
uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) {
uint64_t value = 0;
if(length <= 8) {
value = bit_lib_get_bits(data, position, length);
} else if(length <= 16) {
value = bit_lib_get_bits(data, position, 8) << (length - 8);
value |= bit_lib_get_bits(data, position + 8, length - 8);
} else if(length <= 24) {
value = bit_lib_get_bits(data, position, 8) << (length - 8);
value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16);
value |= bit_lib_get_bits(data, position + 16, length - 16);
} else if(length <= 32) {
value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8);
value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16);
value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24);
value |= bit_lib_get_bits(data, position + 24, length - 24);
} else {
value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8);
value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16);
value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24);
value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32);
value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40);
value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48);
value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56);
value |= (uint64_t)bit_lib_get_bits(data, position + 56, 8) << (length - 64);
value |= bit_lib_get_bits(data, position + 64, length - 64);
}
return value;
}
bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) {
#if !defined __GNUC__
#error Please, implement parity test for non-GCC compilers
#else
switch(parity) {
case BitLibParityEven:
return __builtin_parity(bits);
case BitLibParityOdd:
return !__builtin_parity(bits);
default:
furi_crash("Unknown parity");
}
#endif
}
bool bit_lib_test_parity(
const uint8_t* bits,
size_t position,
uint8_t length,
BitLibParity parity,
uint8_t parity_length) {
uint32_t parity_block;
bool result = true;
const size_t parity_blocks_count = length / parity_length;
for(size_t i = 0; i < parity_blocks_count; ++i) {
switch(parity) {
case BitLibParityEven:
case BitLibParityOdd:
parity_block = bit_lib_get_bits_32(bits, position + i * parity_length, parity_length);
if(!bit_lib_test_parity_32(parity_block, parity)) {
result = false;
}
break;
case BitLibParityAlways0:
if(bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) {
result = false;
}
break;
case BitLibParityAlways1:
if(!bit_lib_get_bit(bits, position + i * parity_length + parity_length - 1)) {
result = false;
}
break;
}
if(!result) break;
}
return result;
}
size_t bit_lib_add_parity(
const uint8_t* data,
size_t position,
uint8_t* dest,
size_t dest_position,
uint8_t source_length,
uint8_t parity_length,
BitLibParity parity) {
uint32_t parity_word = 0;
size_t j = 0, bit_count = 0;
for(int word = 0; word < source_length; word += parity_length - 1) {
for(int bit = 0; bit < parity_length - 1; bit++) {
parity_word = (parity_word << 1) | bit_lib_get_bit(data, position + word + bit);
bit_lib_set_bit(
dest, dest_position + j++, bit_lib_get_bit(data, position + word + bit));
}
// if parity fails then return 0
switch(parity) {
case BitLibParityAlways0:
bit_lib_set_bit(dest, dest_position + j++, 0);
break; // marker bit which should be a 0
case BitLibParityAlways1:
bit_lib_set_bit(dest, dest_position + j++, 1);
break; // marker bit which should be a 1
default:
bit_lib_set_bit(
dest,
dest_position + j++,
(bit_lib_test_parity_32(parity_word, BitLibParityOdd) ^ parity) ^ 1);
break;
}
bit_count += parity_length;
parity_word = 0;
}
// if we got here then all the parities passed
// return bit count
return bit_count;
}
size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n) {
size_t counter = 0;
size_t result_counter = 0;
uint8_t bit_buffer = 0;
uint8_t bit_counter = 0;
while(counter < length) {
if((counter + 1) % n != 0) {
bit_buffer = (bit_buffer << 1) | bit_lib_get_bit(data, position + counter);
bit_counter++;
}
if(bit_counter == 8) {
bit_lib_set_bits(data, position + result_counter, bit_buffer, 8);
bit_counter = 0;
bit_buffer = 0;
result_counter += 8;
}
counter++;
}
if(bit_counter != 0) {
bit_lib_set_bits(data, position + result_counter, bit_buffer, bit_counter);
result_counter += bit_counter;
}
return result_counter;
}
void bit_lib_copy_bits(
uint8_t* data,
size_t position,
size_t length,
const uint8_t* source,
size_t source_position) {
for(size_t i = 0; i < length; ++i) {
bit_lib_set_bit(data, position + i, bit_lib_get_bit(source, source_position + i));
}
}
void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length) {
size_t i = 0;
size_t j = length - 1;
while(i < j) {
bool tmp = bit_lib_get_bit(data, position + i);
bit_lib_set_bit(data, position + i, bit_lib_get_bit(data, position + j));
bit_lib_set_bit(data, position + j, tmp);
i++;
j--;
}
}
uint8_t bit_lib_get_bit_count(uint32_t data) {
#if defined __GNUC__
return __builtin_popcountl(data);
#else
#error Please, implement popcount for non-GCC compilers
#endif
}
void bit_lib_print_bits(const uint8_t* data, size_t length) {
for(size_t i = 0; i < length; ++i) {
printf("%u", bit_lib_get_bit(data, i));
}
}
void bit_lib_print_regions(
const BitLibRegion* regions,
size_t region_count,
const uint8_t* data,
size_t length) {
// print data
bit_lib_print_bits(data, length);
printf("\r\n");
// print regions
for(size_t c = 0; c < length; ++c) {
bool print = false;
for(size_t i = 0; i < region_count; i++) {
if(regions[i].start <= c && c < regions[i].start + regions[i].length) {
print = true;
printf("%c", regions[i].mark);
break;
}
}
if(!print) {
printf(" ");
}
}
printf("\r\n");
// print regions data
for(size_t c = 0; c < length; ++c) {
bool print = false;
for(size_t i = 0; i < region_count; i++) {
if(regions[i].start <= c && c < regions[i].start + regions[i].length) {
print = true;
printf("%u", bit_lib_get_bit(data, c));
break;
}
}
if(!print) {
printf(" ");
}
}
printf("\r\n");
}
uint16_t bit_lib_reverse_16_fast(uint16_t data) {
uint16_t result = 0;
result |= (data & 0x8000) >> 15;
result |= (data & 0x4000) >> 13;
result |= (data & 0x2000) >> 11;
result |= (data & 0x1000) >> 9;
result |= (data & 0x0800) >> 7;
result |= (data & 0x0400) >> 5;
result |= (data & 0x0200) >> 3;
result |= (data & 0x0100) >> 1;
result |= (data & 0x0080) << 1;
result |= (data & 0x0040) << 3;
result |= (data & 0x0020) << 5;
result |= (data & 0x0010) << 7;
result |= (data & 0x0008) << 9;
result |= (data & 0x0004) << 11;
result |= (data & 0x0002) << 13;
result |= (data & 0x0001) << 15;
return result;
}
uint8_t bit_lib_reverse_8_fast(uint8_t byte) {
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
return byte;
}
uint16_t bit_lib_crc8(
uint8_t const* data,
size_t data_size,
uint8_t polynom,
uint8_t init,
bool ref_in,
bool ref_out,
uint8_t xor_out) {
uint8_t crc = init;
for(size_t i = 0; i < data_size; ++i) {
uint8_t byte = data[i];
if(ref_in) bit_lib_reverse_bits(&byte, 0, 8);
crc ^= byte;
for(size_t j = 8; j > 0; --j) {
if(crc & TOPBIT(8)) {
crc = (crc << 1) ^ polynom;
} else {
crc = (crc << 1);
}
}
}
if(ref_out) bit_lib_reverse_bits(&crc, 0, 8);
crc ^= xor_out;
return crc;
}
uint16_t bit_lib_crc16(
uint8_t const* data,
size_t data_size,
uint16_t polynom,
uint16_t init,
bool ref_in,
bool ref_out,
uint16_t xor_out) {
uint16_t crc = init;
for(size_t i = 0; i < data_size; ++i) {
uint8_t byte = data[i];
if(ref_in) byte = bit_lib_reverse_16_fast(byte) >> 8;
for(size_t j = 0; j < 8; ++j) {
bool c15 = (crc >> 15 & 1);
bool bit = (byte >> (7 - j) & 1);
crc <<= 1;
if(c15 ^ bit) crc ^= polynom;
}
}
if(ref_out) crc = bit_lib_reverse_16_fast(crc);
crc ^= xor_out;
return crc;
}

281
lib/nfc/helpers/bit_lib.h Normal file
View file

@ -0,0 +1,281 @@
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TOPBIT(X) (1 << ((X)-1))
typedef enum {
BitLibParityEven,
BitLibParityOdd,
BitLibParityAlways0,
BitLibParityAlways1,
} BitLibParity;
/** @brief Increment and wrap around a value.
* @param index value to increment
* @param length wrap-around range
*/
#define bit_lib_increment_index(index, length) (index = (((index) + 1) % (length)))
/** @brief Test if a bit is set.
* @param data value to test
* @param index bit index to test
*/
#define bit_lib_bit_is_set(data, index) (((data) & (1 << (index))) != 0)
/** @brief Test if a bit is not set.
* @param data value to test
* @param index bit index to test
*/
#define bit_lib_bit_is_not_set(data, index) (((data) & (1 << (index))) == 0)
/** @brief Push a bit into a byte array.
* @param data array to push bit into
* @param data_size array size
* @param bit bit to push
*/
void bit_lib_push_bit(uint8_t* data, size_t data_size, bool bit);
/** @brief Set a bit in a byte array.
* @param data array to set bit in
* @param position The position of the bit to set.
* @param bit bit value to set
*/
void bit_lib_set_bit(uint8_t* data, size_t position, bool bit);
/** @brief Set the bit at the given position to the given value.
* @param data The data to set the bit in.
* @param position The position of the bit to set.
* @param byte The data to set the bit to.
* @param length The length of the data.
*/
void bit_lib_set_bits(uint8_t* data, size_t position, uint8_t byte, uint8_t length);
/** @brief Get the bit of a byte.
* @param data The byte to get the bits from.
* @param position The position of the bit.
* @return The bit.
*/
bool bit_lib_get_bit(const uint8_t* data, size_t position);
/**
* @brief Get the bits of a data, as uint8_t.
* @param data The data to get the bits from.
* @param position The position of the first bit.
* @param length The length of the bits.
* @return The bits.
*/
uint8_t bit_lib_get_bits(const uint8_t* data, size_t position, uint8_t length);
/**
* @brief Get the bits of a data, as uint16_t.
* @param data The data to get the bits from.
* @param position The position of the first bit.
* @param length The length of the bits.
* @return The bits.
*/
uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t length);
/**
* @brief Get the bits of a data, as uint32_t.
* @param data The data to get the bits from.
* @param position The position of the first bit.
* @param length The length of the bits.
* @return The bits.
*/
uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length);
/**
* @brief Get the bits of a data, as uint64_t.
* @param data The data to get the bits from.
* @param position The position of the first bit.
* @param length The length of the bits.
* @return The bits.
*/
uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length);
/**
* @brief Test parity of given bits
* @param bits Bits to test parity of
* @param parity Parity to test against
* @return true if parity is correct, false otherwise
*/
bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity);
/**
* @brief Test parity of bit array, check parity for every parity_length block from start
*
* @param data Bit array
* @param position Start position
* @param length Bit count
* @param parity Parity to test against
* @param parity_length Parity block length
* @return true
* @return false
*/
bool bit_lib_test_parity(
const uint8_t* data,
size_t position,
uint8_t length,
BitLibParity parity,
uint8_t parity_length);
/**
* @brief Add parity to bit array
*
* @param data Source bit array
* @param position Start position
* @param dest Destination bit array
* @param dest_position Destination position
* @param source_length Source bit count
* @param parity_length Parity block length
* @param parity Parity to test against
* @return size_t
*/
size_t bit_lib_add_parity(
const uint8_t* data,
size_t position,
uint8_t* dest,
size_t dest_position,
uint8_t source_length,
uint8_t parity_length,
BitLibParity parity);
/**
* @brief Remove bit every n in array and shift array left. Useful to remove parity.
*
* @param data Bit array
* @param position Start position
* @param length Bit count
* @param n every n bit will be removed
* @return size_t
*/
size_t bit_lib_remove_bit_every_nth(uint8_t* data, size_t position, uint8_t length, uint8_t n);
/**
* @brief Copy bits from source to destination.
*
* @param data destination array
* @param position position in destination array
* @param length length of bits to copy
* @param source source array
* @param source_position position in source array
*/
void bit_lib_copy_bits(
uint8_t* data,
size_t position,
size_t length,
const uint8_t* source,
size_t source_position);
/**
* @brief Reverse bits in bit array
*
* @param data Bit array
* @param position start position
* @param length length of bits to reverse
*/
void bit_lib_reverse_bits(uint8_t* data, size_t position, uint8_t length);
/**
* @brief Count 1 bits in data
*
* @param data
* @return uint8_t set bit count
*/
uint8_t bit_lib_get_bit_count(uint32_t data);
/**
* @brief Print data as bit array
*
* @param data
* @param length
*/
void bit_lib_print_bits(const uint8_t* data, size_t length);
typedef struct {
const char mark;
const size_t start;
const size_t length;
} BitLibRegion;
/**
* @brief Print data as bit array and mark regions. Regions needs to be sorted by start position.
*
* @param regions
* @param region_count
* @param data
* @param length
*/
void bit_lib_print_regions(
const BitLibRegion* regions,
size_t region_count,
const uint8_t* data,
size_t length);
/**
* @brief Reverse bits in uint16_t, faster than generic bit_lib_reverse_bits.
*
* @param data
* @return uint16_t
*/
uint16_t bit_lib_reverse_16_fast(uint16_t data);
/**
* @brief Reverse bits in uint8_t, faster than generic bit_lib_reverse_bits.
*
* @param byte Byte
* @return uint8_t the reversed byte
*/
uint8_t bit_lib_reverse_8_fast(uint8_t byte);
/**
* @brief Slow, but generic CRC8 implementation
*
* @param data
* @param data_size
* @param polynom CRC polynom
* @param init init value
* @param ref_in true if the right bit is older
* @param ref_out true to reverse output
* @param xor_out xor output with this value
* @return uint8_t
*/
uint16_t bit_lib_crc8(
uint8_t const* data,
size_t data_size,
uint8_t polynom,
uint8_t init,
bool ref_in,
bool ref_out,
uint8_t xor_out);
/**
* @brief Slow, but generic CRC16 implementation
*
* @param data
* @param data_size
* @param polynom CRC polynom
* @param init init value
* @param ref_in true if the right bit is older
* @param ref_out true to reverse output
* @param xor_out xor output with this value
* @return uint16_t
*/
uint16_t bit_lib_crc16(
uint8_t const* data,
size_t data_size,
uint16_t polynom,
uint16_t init,
bool ref_in,
bool ref_out,
uint16_t xor_out);
#ifdef __cplusplus
}
#endif

956
lib/nfc/helpers/transport.c Normal file
View file

@ -0,0 +1,956 @@
#include "transport.h"
#include <furi_hal_rtc.h>
#define TAG "Transport parser"
void from_days_to_datetime(uint16_t days, FuriHalRtcDateTime* datetime, uint16_t start_year) {
uint32_t timestamp = days * 24 * 60 * 60;
FuriHalRtcDateTime start_datetime = {0};
start_datetime.year = start_year - 1;
start_datetime.month = 12;
start_datetime.day = 31;
timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime);
furi_hal_rtc_timestamp_to_datetime(timestamp, datetime);
}
void from_minutes_to_datetime(uint32_t minutes, FuriHalRtcDateTime* datetime, uint16_t start_year) {
uint32_t timestamp = minutes * 60;
FuriHalRtcDateTime start_datetime = {0};
start_datetime.year = start_year - 1;
start_datetime.month = 12;
start_datetime.day = 31;
timestamp += furi_hal_rtc_datetime_to_timestamp(&start_datetime);
furi_hal_rtc_timestamp_to_datetime(timestamp, datetime);
}
bool parse_transport_block(MfClassicBlock* block, FuriString* result) {
uint16_t transport_departament = bit_lib_get_bits_16(block->value, 0, 10);
FURI_LOG_D(TAG, "Transport departament: %x", transport_departament);
uint16_t layout_type = bit_lib_get_bits_16(block->value, 52, 4);
if(layout_type == 0xE) {
layout_type = bit_lib_get_bits_16(block->value, 52, 9);
} else if(layout_type == 0xF) {
layout_type = bit_lib_get_bits_16(block->value, 52, 14);
}
FURI_LOG_D(TAG, "Layout type %x", layout_type);
uint16_t card_view = 0;
uint16_t card_type = 0;
uint32_t card_number = 0;
uint8_t card_layout = 0;
uint8_t card_layout2 = 0;
uint16_t card_use_before_date = 0;
uint16_t card_blank_type = 0;
uint32_t card_start_trip_minutes = 0;
uint8_t card_minutes_pass = 0;
uint32_t card_remaining_funds = 0;
uint16_t card_validator = 0;
uint8_t card_blocked = 0;
uint32_t card_hash = 0;
switch(layout_type) {
case 0x02: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_use_before_date = bit_lib_get_bits_16(block->value, 56, 16); //202
uint8_t card_benefit_code = bit_lib_get_bits(block->value, 72, 8); //124
uint32_t card_rfu1 = bit_lib_get_bits_32(block->value, 80, 32); //rfu1
uint16_t card_crc16 = bit_lib_get_bits_16(block->value, 112, 16); //501.1
card_blocked = bit_lib_get_bits(block->value, 128, 1); //303
uint16_t card_start_trip_time = bit_lib_get_bits_16(block->value, 177, 12); //403
uint16_t card_start_trip_date = bit_lib_get_bits_16(block->value, 189, 16); //402
uint16_t card_valid_from_date = bit_lib_get_bits_16(block->value, 157, 16); //311
uint16_t card_valid_by_date = bit_lib_get_bits_16(block->value, 173, 16); //312
uint8_t card_start_trip_seconds = bit_lib_get_bits(block->value, 189, 6); //406
uint8_t card_transport_type1 = bit_lib_get_bits(block->value, 180, 2); //421.1
uint8_t card_transport_type2 = bit_lib_get_bits(block->value, 182, 2); //421.2
uint8_t card_transport_type3 = bit_lib_get_bits(block->value, 184, 2); //421.3
uint8_t card_transport_type4 = bit_lib_get_bits(block->value, 186, 2); //421.4
uint16_t card_use_with_date = bit_lib_get_bits_16(block->value, 189, 16); //205
uint8_t card_route = bit_lib_get_bits(block->value, 205, 1); //424
uint16_t card_validator1 = bit_lib_get_bits_16(block->value, 206, 15); //422.1
card_validator = bit_lib_get_bits_16(block->value, 205, 16); //422
uint16_t card_total_trips = bit_lib_get_bits_16(block->value, 221, 16); //331
uint8_t card_write_enabled = bit_lib_get_bits(block->value, 237, 1); //write_enabled
uint8_t card_rfu2 = bit_lib_get_bits(block->value, 238, 2); //rfu2
uint16_t card_crc16_2 = bit_lib_get_bits_16(block->value, 240, 16); //501.2
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
card_view,
card_type,
card_number,
card_use_before_date,
card_benefit_code,
card_rfu1,
card_crc16,
card_blocked,
card_start_trip_time,
card_start_trip_date,
card_valid_from_date,
card_valid_by_date,
card_start_trip_seconds,
card_transport_type1,
card_transport_type2,
card_transport_type3,
card_transport_type4,
card_use_with_date,
card_route,
card_validator1,
card_validator,
card_total_trips,
card_write_enabled,
card_rfu2,
card_crc16_2);
break;
}
case 0x06: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_use_before_date = bit_lib_get_bits_16(block->value, 56, 16); //202
uint8_t card_geozone_a = bit_lib_get_bits(block->value, 72, 4); //GeoZoneA
uint8_t card_geozone_b = bit_lib_get_bits(block->value, 76, 4); //GeoZoneB
card_blank_type = bit_lib_get_bits_16(block->value, 80, 10); //121.
uint16_t card_type_of_extended = bit_lib_get_bits_16(block->value, 90, 10); //122
uint32_t card_rfu1 = bit_lib_get_bits_16(block->value, 100, 12); //rfu1
uint16_t card_crc16 = bit_lib_get_bits_16(block->value, 112, 16); //501.1
card_blocked = bit_lib_get_bits(block->value, 128, 1); //303
uint16_t card_start_trip_time = bit_lib_get_bits_16(block->value, 129, 12); //403
uint16_t card_start_trip_date = bit_lib_get_bits_16(block->value, 141, 16); //402
uint16_t card_valid_from_date = bit_lib_get_bits_16(block->value, 157, 16); //311
uint16_t card_valid_by_date = bit_lib_get_bits_16(block->value, 173, 16); //312
uint16_t card_company = bit_lib_get_bits(block->value, 189, 4); //Company
uint8_t card_validator1 = bit_lib_get_bits(block->value, 193, 4); //422.1
uint16_t card_remaining_trips = bit_lib_get_bits_16(block->value, 197, 10); //321
uint8_t card_units = bit_lib_get_bits(block->value, 207, 6); //Units
uint16_t card_validator2 = bit_lib_get_bits_16(block->value, 213, 10); //422.2
uint16_t card_total_trips = bit_lib_get_bits_16(block->value, 223, 16); //331
uint8_t card_extended = bit_lib_get_bits(block->value, 239, 1); //123
uint16_t card_crc16_2 = bit_lib_get_bits_16(block->value, 240, 16); //501.2
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
card_view,
card_type,
card_number,
card_use_before_date,
card_geozone_a,
card_geozone_b,
card_blank_type,
card_type_of_extended,
card_rfu1,
card_crc16,
card_blocked,
card_start_trip_time,
card_start_trip_date,
card_valid_from_date,
card_valid_by_date,
card_company,
card_validator1,
card_remaining_trips,
card_units,
card_validator2,
card_total_trips,
card_extended,
card_crc16_2);
card_validator = card_validator1 * 1024 + card_validator2;
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
(card_start_trip_date - 1) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s,
1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d of %d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_remaining_trips,
card_total_trips,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x08: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_use_before_date = bit_lib_get_bits_16(block->value, 56, 16); //202
uint64_t card_rfu1 = bit_lib_get_bits_64(block->value, 72, 56); //rfu1
uint16_t card_valid_from_date = bit_lib_get_bits_16(block->value, 128, 16); //311
uint8_t card_valid_for_days = bit_lib_get_bits(block->value, 144, 8); //313
uint8_t card_requires_activation = bit_lib_get_bits(block->value, 152, 1); //301
uint8_t card_rfu2 = bit_lib_get_bits(block->value, 153, 7); //rfu2
uint8_t card_remaining_trips1 = bit_lib_get_bits(block->value, 160, 8); //321.1
uint8_t card_remaining_trips = bit_lib_get_bits(block->value, 168, 8); //321
uint8_t card_validator1 = bit_lib_get_bits(block->value, 193, 2); //422.1
uint16_t card_validator = bit_lib_get_bits_16(block->value, 177, 15); //422
card_hash = bit_lib_get_bits_32(block->value, 192, 32); //502
uint32_t card_rfu3 = bit_lib_get_bits_32(block->value, 224, 32); //rfu3
FURI_LOG_D(
TAG,
"%x %x %lx %x %llx %x %x %x %x %x %x %x %x %lx %x %lx",
card_view,
card_type,
card_number,
card_use_before_date,
card_rfu1,
card_valid_from_date,
card_valid_for_days,
card_requires_activation,
card_rfu2,
card_remaining_trips1,
card_remaining_trips,
card_validator1,
card_validator,
card_hash,
card_valid_from_date,
card_rfu3);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrips left: %d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_remaining_trips,
card_validator);
break;
}
case 0x0A: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
uint16_t card_valid_from_date = bit_lib_get_bits_16(block->value, 64, 12); //311
uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->value, 76, 19); //314
uint8_t card_requires_activation = bit_lib_get_bits(block->value, 95, 1); //301
card_start_trip_minutes = bit_lib_get_bits_32(block->value, 96, 19); //405
card_minutes_pass = bit_lib_get_bits(block->value, 119, 7); //412
uint8_t card_transport_type_flag = bit_lib_get_bits(block->value, 126, 2); //421.0
uint8_t card_remaining_trips = bit_lib_get_bits(block->value, 128, 8); //321
uint16_t card_validator = bit_lib_get_bits_16(block->value, 136, 16); //422
uint8_t card_transport_type1 = bit_lib_get_bits(block->value, 152, 2); //421.1
uint8_t card_transport_type2 = bit_lib_get_bits(block->value, 154, 2); //421.2
uint8_t card_transport_type3 = bit_lib_get_bits(block->value, 156, 2); //421.3
uint8_t card_transport_type4 = bit_lib_get_bits(block->value, 158, 2); //421.4
card_hash = bit_lib_get_bits_32(block->value, 192, 32); //502
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %lx %x %lx %x %x %x %x %x %x %x %x %lx",
card_view,
card_type,
card_number,
card_use_before_date,
card_valid_from_date,
card_valid_for_minutes,
card_requires_activation,
card_start_trip_minutes,
card_minutes_pass,
card_transport_type_flag,
card_remaining_trips,
card_validator,
card_transport_type1,
card_transport_type2,
card_transport_type3,
card_transport_type4,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 2016);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_remaining_trips,
card_validator);
break;
}
case 0x0C: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_use_before_date = bit_lib_get_bits_16(block->value, 56, 16); //202
uint64_t card_rfu1 = bit_lib_get_bits_64(block->value, 72, 56); //rfu1
uint16_t card_valid_from_date = bit_lib_get_bits_16(block->value, 128, 16); //311
uint8_t card_valid_for_days = bit_lib_get_bits(block->value, 144, 8); //313
uint8_t card_requires_activation = bit_lib_get_bits(block->value, 152, 1); //301
uint16_t card_rfu2 = bit_lib_get_bits_16(block->value, 153, 13); //rfu2
uint16_t card_remaining_trips = bit_lib_get_bits_16(block->value, 166, 10); //321
uint16_t card_validator = bit_lib_get_bits_16(block->value, 176, 16); //422
card_hash = bit_lib_get_bits_32(block->value, 192, 32); //502
uint16_t card_start_trip_date = bit_lib_get_bits_16(block->value, 224, 16); //402
uint16_t card_start_trip_time = bit_lib_get_bits_16(block->value, 240, 11); //403
uint8_t card_transport_type = bit_lib_get_bits(block->value, 251, 2); //421
uint8_t card_rfu3 = bit_lib_get_bits(block->value, 253, 2); //rfu3
uint8_t card_transfer_in_metro = bit_lib_get_bits(block->value, 255, 1); //432
FURI_LOG_D(
TAG,
"%x %x %lx %x %llx %x %x %x %x %x %x %x %x %x %x %x",
card_view,
card_type,
card_number,
card_use_before_date,
card_rfu1,
card_valid_from_date,
card_valid_for_days,
card_requires_activation,
card_rfu2,
card_remaining_trips,
card_validator,
card_start_trip_date,
card_start_trip_time,
card_transport_type,
card_rfu3,
card_transfer_in_metro);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
(card_start_trip_date - 1) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s,
1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_remaining_trips,
card_validator);
break;
}
case 0x0D: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
uint8_t card_rfu1 = bit_lib_get_bits(block->value, 56, 8); //rfu1
card_use_before_date = bit_lib_get_bits_16(block->value, 64, 16); //202
uint16_t card_valid_for_time = bit_lib_get_bits_16(block->value, 80, 11); //316
uint8_t card_rfu2 = bit_lib_get_bits(block->value, 91, 5); //rfu2
uint16_t card_use_before_date2 = bit_lib_get_bits_16(block->value, 96, 16); //202.2
uint16_t card_valid_for_time2 = bit_lib_get_bits_16(block->value, 123, 11); //316.2
uint8_t card_rfu3 = bit_lib_get_bits(block->value, 123, 5); //rfu3
uint16_t card_valid_from_date = bit_lib_get_bits_16(block->value, 128, 16); //311
uint8_t card_valid_for_days = bit_lib_get_bits(block->value, 144, 8); //313
uint8_t card_requires_activation = bit_lib_get_bits(block->value, 152, 1); //301
uint8_t card_rfu4 = bit_lib_get_bits(block->value, 153, 2); //rfu4
uint8_t card_passage_5_minutes = bit_lib_get_bits(block->value, 155, 5); //413
uint8_t card_transport_type1 = bit_lib_get_bits(block->value, 160, 2); //421.1
uint8_t card_passage_in_metro = bit_lib_get_bits(block->value, 162, 1); //431
uint8_t card_passages_ground_transport = bit_lib_get_bits(block->value, 163, 3); //433
uint16_t card_remaining_trips = bit_lib_get_bits_16(block->value, 166, 10); //321
uint16_t card_validator = bit_lib_get_bits_16(block->value, 176, 16); //422
card_hash = bit_lib_get_bits_32(block->value, 192, 32); //502
uint16_t card_start_trip_date = bit_lib_get_bits_16(block->value, 224, 16); //402
uint16_t card_start_trip_time = bit_lib_get_bits_16(block->value, 240, 11); //403
uint8_t card_transport_type2 = bit_lib_get_bits(block->value, 251, 2); //421.2
uint8_t card_rfu5 = bit_lib_get_bits(block->value, 253, 2); //rfu5
uint8_t card_transfer_in_metro = bit_lib_get_bits(block->value, 255, 1); //432
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
card_view,
card_type,
card_number,
card_layout,
card_rfu1,
card_use_before_date,
card_valid_for_time,
card_rfu2,
card_use_before_date2,
card_valid_for_time2,
card_rfu3,
card_valid_from_date,
card_valid_for_days,
card_requires_activation,
card_rfu4,
card_passage_5_minutes,
card_transport_type1,
card_passage_in_metro,
card_passages_ground_transport,
card_remaining_trips,
card_validator,
card_start_trip_date,
card_start_trip_time,
card_transport_type2,
card_rfu5,
card_transfer_in_metro);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
(card_start_trip_date - 1) * 24 * 60 + card_start_trip_time,
&card_start_trip_minutes_s,
1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nTrips left: %d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_remaining_trips,
card_validator);
break;
}
case 0x1C1: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_layout2 = bit_lib_get_bits(block->value, 56, 5); //112
card_use_before_date = bit_lib_get_bits_16(block->value, 61, 16); //202.
card_blank_type = bit_lib_get_bits_16(block->value, 77, 10); //121.
card_validator = bit_lib_get_bits_16(block->value, 128, 16); //422
uint16_t card_start_trip_date = bit_lib_get_bits_16(block->value, 144, 16); //402
uint16_t card_start_trip_time = bit_lib_get_bits_16(block->value, 160, 11); //403
uint8_t card_transport_type1 = bit_lib_get_bits(block->value, 171, 2); //421.1
uint8_t card_transport_type2 = bit_lib_get_bits(block->value, 173, 2); //421.2
uint8_t card_transfer_in_metro = bit_lib_get_bits(block->value, 177, 1); //432
uint8_t card_passage_in_metro = bit_lib_get_bits(block->value, 178, 1); //431
uint8_t card_passages_ground_transport = bit_lib_get_bits(block->value, 179, 3); //433
card_minutes_pass = bit_lib_get_bits(block->value, 185, 8); //412.
card_remaining_funds = bit_lib_get_bits_32(block->value, 196, 19) / 100; //322
uint8_t card_fare_trip = bit_lib_get_bits(block->value, 215, 2); //441
card_blocked = bit_lib_get_bits(block->value, 202, 1); //303
uint8_t card_zoo = bit_lib_get_bits(block->value, 218, 1); //zoo
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %lx %x %x %x %lx",
card_view,
card_type,
card_number,
card_layout,
card_layout2,
card_use_before_date,
card_blank_type,
card_validator,
card_start_trip_date,
card_start_trip_time,
card_transport_type1,
card_transport_type2,
card_transfer_in_metro,
card_passage_in_metro,
card_passages_ground_transport,
card_minutes_pass,
card_remaining_funds,
card_fare_trip,
card_blocked,
card_zoo,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
card_start_trip_minutes - (2 * 24 * 60), &card_start_trip_minutes_s, 1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x1C2: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_layout2 = bit_lib_get_bits(block->value, 56, 5); //112
uint16_t card_type_of_extended = bit_lib_get_bits_16(block->value, 61, 10); //122
card_use_before_date = bit_lib_get_bits_16(block->value, 71, 16); //202.
card_blank_type = bit_lib_get_bits_16(block->value, 87, 10); //121.
uint16_t card_valid_to_date = bit_lib_get_bits_16(block->value, 97, 16); //311
uint16_t card_activate_during = bit_lib_get_bits_16(block->value, 113, 9); //302
uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->value, 131, 20); //314
card_minutes_pass = bit_lib_get_bits(block->value, 154, 8); //412.
uint8_t card_transport_type = bit_lib_get_bits(block->value, 163, 2); //421
uint8_t card_passage_in_metro = bit_lib_get_bits(block->value, 165, 1); //431
uint8_t card_transfer_in_metro = bit_lib_get_bits(block->value, 166, 1); //432
uint16_t card_remaining_trips = bit_lib_get_bits_16(block->value, 167, 10); //321
card_validator = bit_lib_get_bits_16(block->value, 177, 16); //422
uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->value, 196, 20); //404
uint8_t card_requires_activation = bit_lib_get_bits(block->value, 216, 1); //301
card_blocked = bit_lib_get_bits(block->value, 217, 1); //303
uint8_t card_extended = bit_lib_get_bits(block->value, 218, 1); //123
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %lx %x %x %x %lx",
card_view,
card_type,
card_number,
card_layout,
card_layout2,
card_type_of_extended,
card_use_before_date,
card_blank_type,
card_valid_to_date,
card_activate_during,
card_valid_for_minutes,
card_minutes_pass,
card_transport_type,
card_passage_in_metro,
card_transfer_in_metro,
card_remaining_trips,
card_validator,
card_start_trip_neg_minutes,
card_requires_activation,
card_blocked,
card_extended,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
(card_valid_to_date - 1) * 24 * 60 + card_valid_for_minutes -
card_start_trip_neg_minutes,
&card_start_trip_minutes_s,
2016); //-time
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x1C3: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_layout2 = bit_lib_get_bits(block->value, 56, 5); //112
card_use_before_date = bit_lib_get_bits_16(block->value, 61, 16); //202
card_blank_type = bit_lib_get_bits_16(block->value, 77, 10); //121
card_remaining_funds = bit_lib_get_bits_32(block->value, 188, 22) / 100; //322
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502
card_validator = bit_lib_get_bits_16(block->value, 128, 16); //422
card_start_trip_minutes = bit_lib_get_bits_32(block->value, 144, 23); //405
uint8_t card_fare_trip = bit_lib_get_bits(block->value, 210, 2); //441
card_minutes_pass = bit_lib_get_bits(block->value, 171, 7); //412
uint8_t card_transport_type_flag = bit_lib_get_bits(block->value, 178, 2); //421.0
uint8_t card_transport_type1 = bit_lib_get_bits(block->value, 180, 2); //421.1
uint8_t card_transport_type2 = bit_lib_get_bits(block->value, 182, 2); //421.2
uint8_t card_transport_type3 = bit_lib_get_bits(block->value, 184, 2); //421.3
uint8_t card_transport_type4 = bit_lib_get_bits(block->value, 186, 2); //421.4
card_blocked = bit_lib_get_bits(block->value, 212, 1); //303
FURI_LOG_D(
TAG,
"Card view: %x, type: %x, number: %lx, layout: %x, layout2: %x, use before date: %x, blank type: %x, remaining funds: %lx, hash: %lx, validator: %x, start trip minutes: %lx, fare trip: %x, minutes pass: %x, transport type flag: %x, transport type1: %x, transport type2: %x, transport type3: %x, transport type4: %x, blocked: %x",
card_view,
card_type,
card_number,
card_layout,
card_layout2,
card_use_before_date,
card_blank_type,
card_remaining_funds,
card_hash,
card_validator,
card_start_trip_minutes,
card_fare_trip,
card_minutes_pass,
card_transport_type_flag,
card_transport_type1,
card_transport_type2,
card_transport_type3,
card_transport_type4,
card_blocked);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 1992);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(card_start_trip_minutes, &card_start_trip_minutes_s, 2016);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_remaining_funds,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x1C4: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_layout2 = bit_lib_get_bits(block->value, 56, 5); //112
uint16_t card_type_of_extended = bit_lib_get_bits_16(block->value, 61, 10); //122
card_use_before_date = bit_lib_get_bits_16(block->value, 71, 13); //202.
card_blank_type = bit_lib_get_bits_16(block->value, 84, 10); //121.
uint16_t card_valid_to_date = bit_lib_get_bits_16(block->value, 94, 13); //311
uint16_t card_activate_during = bit_lib_get_bits_16(block->value, 107, 9); //302
uint16_t card_extension_counter = bit_lib_get_bits_16(block->value, 116, 10); //304
uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->value, 128, 20); //314
card_minutes_pass = bit_lib_get_bits(block->value, 158, 7); //412.
uint8_t card_transport_type_flag = bit_lib_get_bits(block->value, 178, 2); //421.0
uint8_t card_transport_type1 = bit_lib_get_bits(block->value, 180, 2); //421.1
uint8_t card_transport_type2 = bit_lib_get_bits(block->value, 182, 2); //421.2
uint8_t card_transport_type3 = bit_lib_get_bits(block->value, 184, 2); //421.3
uint8_t card_transport_type4 = bit_lib_get_bits(block->value, 186, 2); //421.4
uint16_t card_remaining_trips = bit_lib_get_bits_16(block->value, 169, 10); //321
card_validator = bit_lib_get_bits_16(block->value, 179, 16); //422
uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->value, 195, 20); //404
uint8_t card_requires_activation = bit_lib_get_bits(block->value, 215, 1); //301
card_blocked = bit_lib_get_bits(block->value, 216, 1); //303
uint8_t card_extended = bit_lib_get_bits(block->value, 217, 1); //123
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %x %x %x %x %x %lx %x %x %x %lx",
card_view,
card_type,
card_number,
card_layout,
card_layout2,
card_type_of_extended,
card_use_before_date,
card_blank_type,
card_valid_to_date,
card_activate_during,
card_extension_counter,
card_valid_for_minutes,
card_minutes_pass,
card_transport_type_flag,
card_transport_type1,
card_transport_type2,
card_transport_type3,
card_transport_type4,
card_remaining_trips,
card_validator,
card_start_trip_neg_minutes,
card_requires_activation,
card_blocked,
card_extended,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2016);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
(card_use_before_date - 1) * 24 * 60 + card_valid_for_minutes -
card_start_trip_neg_minutes,
&card_start_trip_minutes_s,
2016); //-time
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x1C5: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_layout2 = bit_lib_get_bits(block->value, 56, 5); //112
card_use_before_date = bit_lib_get_bits_16(block->value, 61, 13); //202.
card_blank_type = bit_lib_get_bits_16(block->value, 74, 10); //121.
uint32_t card_valid_to_time = bit_lib_get_bits_32(block->value, 84, 23); //317
uint16_t card_extension_counter = bit_lib_get_bits_16(block->value, 107, 10); //304
card_start_trip_minutes = bit_lib_get_bits_32(block->value, 128, 23); //405
uint8_t card_metro_ride_with = bit_lib_get_bits(block->value, 151, 7); //414
card_minutes_pass = bit_lib_get_bits(block->value, 158, 7); //412.
card_remaining_funds = bit_lib_get_bits_32(block->value, 167, 19) / 100; //322
card_validator = bit_lib_get_bits_16(block->value, 186, 16); //422
card_blocked = bit_lib_get_bits(block->value, 202, 1); //303
uint16_t card_route = bit_lib_get_bits_16(block->value, 204, 12); //424
uint8_t card_passages_ground_transport = bit_lib_get_bits(block->value, 216, 7); //433
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %lx %x %lx %x %x %lx %x %x %x %x %lx",
card_view,
card_type,
card_number,
card_layout,
card_layout2,
card_use_before_date,
card_blank_type,
card_valid_to_time,
card_extension_counter,
card_start_trip_minutes,
card_metro_ride_with,
card_minutes_pass,
card_remaining_funds,
card_validator,
card_blocked,
card_route,
card_passages_ground_transport,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date, &card_use_before_date_s, 2019);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
card_start_trip_minutes - (24 * 60), &card_start_trip_minutes_s, 2019);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nBalance: %ld rub\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_remaining_funds,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x1C6: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
card_layout2 = bit_lib_get_bits(block->value, 56, 5); //112
uint16_t card_type_of_extended = bit_lib_get_bits_16(block->value, 61, 10); //122
card_use_before_date = bit_lib_get_bits_16(block->value, 71, 13); //202.
card_blank_type = bit_lib_get_bits_16(block->value, 84, 10); //121.
uint32_t card_valid_from_date = bit_lib_get_bits_32(block->value, 94, 23); //311
uint16_t card_extension_counter = bit_lib_get_bits_16(block->value, 117, 10); //304
uint32_t card_valid_for_minutes = bit_lib_get_bits_32(block->value, 128, 20); //314
uint32_t card_start_trip_neg_minutes = bit_lib_get_bits_32(block->value, 148, 20); //404
uint8_t card_metro_ride_with = bit_lib_get_bits(block->value, 168, 7); //414
card_minutes_pass = bit_lib_get_bits(block->value, 175, 7); //412.
uint16_t card_remaining_trips = bit_lib_get_bits_16(block->value, 182, 7); //321
card_validator = bit_lib_get_bits_16(block->value, 189, 16); //422
card_blocked = bit_lib_get_bits(block->value, 205, 1); //303
uint8_t card_extended = bit_lib_get_bits(block->value, 206, 1); //123
uint16_t card_route = bit_lib_get_bits_16(block->value, 212, 12); //424
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %lx %x %lx %lx %x %x %x %x %x %x %x %lx",
card_view,
card_type,
card_number,
card_layout,
card_layout2,
card_type_of_extended,
card_use_before_date,
card_blank_type,
card_valid_from_date,
card_extension_counter,
card_valid_for_minutes,
card_start_trip_neg_minutes,
card_metro_ride_with,
card_minutes_pass,
card_remaining_trips,
card_validator,
card_blocked,
card_extended,
card_route,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_use_before_date - 1, &card_use_before_date_s, 2019);
FuriHalRtcDateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
card_valid_from_date + card_valid_for_minutes - card_start_trip_neg_minutes - 24 * 60,
&card_start_trip_minutes_s,
2019); //-time
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nTrip from: %02d.%02d.%04d %02d:%02d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
card_start_trip_minutes_s.hour,
card_start_trip_minutes_s.minute,
card_validator);
break;
}
case 0x3CCB: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
uint16_t card_tech_code = bit_lib_get_bits_32(block->value, 56, 10); //tech_code
uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->value, 66, 16); //311
uint16_t card_valid_by_date = bit_lib_get_bits_16(block->value, 82, 16); //312
uint8_t card_interval = bit_lib_get_bits(block->value, 98, 4); //interval
uint16_t card_app_code1 = bit_lib_get_bits_16(block->value, 102, 16); //app_code1
uint16_t card_hash1 = bit_lib_get_bits_16(block->value, 112, 16); //502.1
uint16_t card_type1 = bit_lib_get_bits_16(block->value, 128, 10); //type1
uint16_t card_app_code2 = bit_lib_get_bits_16(block->value, 138, 10); //app_code2
uint16_t card_type2 = bit_lib_get_bits_16(block->value, 148, 10); //type2
uint16_t card_app_code3 = bit_lib_get_bits_16(block->value, 158, 10); //app_code3
uint16_t card_type3 = bit_lib_get_bits_16(block->value, 148, 10); //type3
uint16_t card_app_code4 = bit_lib_get_bits_16(block->value, 168, 10); //app_code4
uint16_t card_type4 = bit_lib_get_bits_16(block->value, 178, 10); //type4
card_hash = bit_lib_get_bits_32(block->value, 224, 32); //502.2
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %lx",
card_view,
card_type,
card_number,
card_layout,
card_tech_code,
card_use_before_date,
card_blank_type,
card_valid_to_minutes,
card_valid_by_date,
card_interval,
card_app_code1,
card_hash1,
card_type1,
card_app_code2,
card_type2,
card_app_code3,
card_type3,
card_app_code4,
card_type4,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_validator);
break;
}
case 0x3C0B: {
card_view = bit_lib_get_bits_16(block->value, 0, 10); //101
card_type = bit_lib_get_bits_16(block->value, 10, 10); //102
card_number = bit_lib_get_bits_32(block->value, 20, 32); //201
card_layout = bit_lib_get_bits(block->value, 52, 4); //111
uint16_t card_tech_code = bit_lib_get_bits_32(block->value, 56, 10); //tech_code
uint16_t card_valid_to_minutes = bit_lib_get_bits_16(block->value, 66, 16); //311
uint16_t card_valid_by_date = bit_lib_get_bits_16(block->value, 82, 16); //312
uint16_t card_hash = bit_lib_get_bits_16(block->value, 112, 16); //502.1
FURI_LOG_D(
TAG,
"%x %x %lx %x %x %x %x %x %x %x",
card_view,
card_type,
card_number,
card_layout,
card_tech_code,
card_use_before_date,
card_blank_type,
card_valid_to_minutes,
card_valid_by_date,
card_hash);
FuriHalRtcDateTime card_use_before_date_s = {0};
from_days_to_datetime(card_valid_by_date - 1, &card_use_before_date_s, 1992);
furi_string_printf(
result,
"Number: %010lu\nValid for: %02d.%02d.%04d\nValidator: %05d",
card_number,
card_use_before_date_s.day,
card_use_before_date_s.month,
card_use_before_date_s.year,
card_validator);
break;
}
default:
return false;
}
return true;
}

View file

@ -0,0 +1,7 @@
#pragma once
#include <furi_hal_rtc.h>
#include <nfc/helpers/bit_lib.h>
#include <nfc_worker_i.h>
bool parse_transport_block(MfClassicBlock* block, FuriString* result);

View file

@ -50,14 +50,14 @@ void nfc_device_free(NfcDevice* nfc_dev) {
static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_string) {
if(dev->format == NfcDeviceSaveFormatUid) {
furi_string_set(format_string, "UID");
} else if(dev->format == NfcDeviceSaveFormatBankCard) {
furi_string_set(format_string, "Bank card");
} else if(dev->format == NfcDeviceSaveFormatMifareUl) {
furi_string_set(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true));
} else if(dev->format == NfcDeviceSaveFormatMifareClassic) {
furi_string_set(format_string, "Mifare Classic");
} else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
furi_string_set(format_string, "Mifare DESFire");
} else if(dev->format == NfcDeviceSaveFormatBankCard) {
furi_string_set(format_string, "Bank card");
} else if(dev->format == NfcDeviceSaveFormatNfcV) {
furi_string_set(format_string, "ISO15693");
} else {
@ -71,11 +71,6 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_st
dev->dev_data.protocol = NfcDeviceProtocolUnknown;
return true;
}
if(furi_string_start_with_str(format_string, "Bank card")) {
dev->format = NfcDeviceSaveFormatBankCard;
dev->dev_data.protocol = NfcDeviceProtocolEMV;
return true;
}
// Check Mifare Ultralight types
for(MfUltralightType type = MfUltralightTypeUnknown; type < MfUltralightTypeNum; type++) {
if(furi_string_equal(format_string, nfc_mf_ul_type(type, true))) {
@ -95,6 +90,11 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_st
dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire;
return true;
}
if(furi_string_start_with_str(format_string, "Bank card")) {
dev->format = NfcDeviceSaveFormatBankCard;
dev->dev_data.protocol = NfcDeviceProtocolEMV;
return true;
}
if(furi_string_start_with_str(format_string, "ISO15693")) {
dev->format = NfcDeviceSaveFormatNfcV;
dev->dev_data.protocol = NfcDeviceProtocolNfcV;

View file

@ -29,20 +29,20 @@ typedef void (*NfcLoadingCallback)(void* context, bool state);
typedef enum {
NfcDeviceProtocolUnknown,
NfcDeviceProtocolEMV,
NfcDeviceProtocolMifareUl,
NfcDeviceProtocolMifareClassic,
NfcDeviceProtocolMifareDesfire,
NfcDeviceProtocolNfcV
NfcDeviceProtocolNfcV,
NfcDeviceProtocolEMV,
} NfcProtocol;
typedef enum {
NfcDeviceSaveFormatUid,
NfcDeviceSaveFormatBankCard,
NfcDeviceSaveFormatMifareUl,
NfcDeviceSaveFormatMifareClassic,
NfcDeviceSaveFormatMifareDesfire,
NfcDeviceSaveFormatNfcV,
NfcDeviceSaveFormatBankCard,
} NfcDeviceSaveFormat;
typedef struct {

View file

@ -15,14 +15,14 @@ const char* nfc_get_dev_type(FuriHalNfcType type) {
}
const char* nfc_guess_protocol(NfcProtocol protocol) {
if(protocol == NfcDeviceProtocolEMV) {
return "EMV bank card";
if(protocol == NfcDeviceProtocolMifareClassic) {
return "Mifare Classic";
} else if(protocol == NfcDeviceProtocolMifareUl) {
return "Mifare Ultral/NTAG";
} else if(protocol == NfcDeviceProtocolMifareClassic) {
return "Mifare Classic";
} else if(protocol == NfcDeviceProtocolMifareDesfire) {
return "Mifare DESFire";
} else if(protocol == NfcDeviceProtocolEMV) {
return "EMV bank card";
} else {
return "Unrecognized";
}

View file

@ -3,10 +3,10 @@
#include "plantain_parser.h"
#include "troika_parser.h"
#include "plantain_4k_parser.h"
#include "troika_4k_parser.h"
#include "two_cities.h"
#include "all_in_one.h"
#include "opal.h"
#include "social_card_moscow_parser.h"
NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
[NfcSupportedCardTypePlantain] =
@ -23,6 +23,13 @@ NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
.read = troika_parser_read,
.parse = troika_parser_parse,
},
[NfcSupportedCardTypeSocialCardMoscow] =
{
.protocol = NfcDeviceProtocolMifareClassic,
.verify = social_card_moscow_parser_verify,
.read = social_card_moscow_parser_read,
.parse = social_card_moscow_parser_parse,
},
[NfcSupportedCardTypePlantain4K] =
{
.protocol = NfcDeviceProtocolMifareClassic,
@ -30,13 +37,6 @@ NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
.read = plantain_4k_parser_read,
.parse = plantain_4k_parser_parse,
},
[NfcSupportedCardTypeTroika4K] =
{
.protocol = NfcDeviceProtocolMifareClassic,
.verify = troika_4k_parser_verify,
.read = troika_4k_parser_read,
.parse = troika_4k_parser_parse,
},
[NfcSupportedCardTypeTwoCities] =
{
.protocol = NfcDeviceProtocolMifareClassic,
@ -58,7 +58,6 @@ NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
.read = stub_parser_verify_read,
.parse = opal_parser_parse,
},
};
bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) {

View file

@ -11,8 +11,8 @@ extern "C" {
typedef enum {
NfcSupportedCardTypePlantain,
NfcSupportedCardTypeTroika,
NfcSupportedCardTypeSocialCardMoscow,
NfcSupportedCardTypePlantain4K,
NfcSupportedCardTypeTroika4K,
NfcSupportedCardTypeTwoCities,
NfcSupportedCardTypeAllInOne,
NfcSupportedCardTypeOpal,

View file

@ -0,0 +1,131 @@
#include "nfc_supported_card.h"
#include <gui/modules/widget.h>
#include <nfc_worker_i.h>
#include <nfc/helpers/transport.h>
#include <nfc/helpers/bit_lib.h>
static const MfClassicAuthContext social_card_moscow_keys[] = {
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0x7de02a7f6025},
{.sector = 1, .key_a = 0x2735fc181807, .key_b = 0xbf23a53c1f63},
{.sector = 2, .key_a = 0x2aba9519f574, .key_b = 0xcb9a1f2d7368},
{.sector = 3, .key_a = 0x84fd7f7a12b6, .key_b = 0xc7c0adb3284f},
{.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
{.sector = 5, .key_a = 0x186d8c4b93f9, .key_b = 0x9f131d8c2057},
{.sector = 6, .key_a = 0x3a4bba8adaf0, .key_b = 0x67362d90f973},
{.sector = 7, .key_a = 0x8765b17968a2, .key_b = 0x6202a38f69e2},
{.sector = 8, .key_a = 0x40ead80721ce, .key_b = 0x100533b89331},
{.sector = 9, .key_a = 0x0db5e6523f7c, .key_b = 0x653a87594079},
{.sector = 10, .key_a = 0x51119dae5216, .key_b = 0xd8a274b2e026},
{.sector = 11, .key_a = 0x51119dae5216, .key_b = 0xd8a274b2e026},
{.sector = 12, .key_a = 0x51119dae5216, .key_b = 0xd8a274b2e026},
{.sector = 13, .key_a = 0xa0a1a2a3a4a5, .key_b = 0x7de02a7f6025},
{.sector = 14, .key_a = 0xa0a1a2a3a4a5, .key_b = 0x7de02a7f6025},
{.sector = 15, .key_a = 0xa0a1a2a3a4a5, .key_b = 0x7de02a7f6025},
{.sector = 16, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 17, .key_a = 0x2aba9519f574, .key_b = 0xcb9a1f2d7368},
{.sector = 18, .key_a = 0x84fd7f7a12b6, .key_b = 0xc7c0adb3284f},
{.sector = 19, .key_a = 0x2aba9519f574, .key_b = 0xcb9a1f2d7368},
{.sector = 20, .key_a = 0x84fd7f7a12b6, .key_b = 0xc7c0adb3284f},
{.sector = 21, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 22, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 23, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 24, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 25, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 26, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 27, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 28, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 29, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 30, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 31, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 32, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 33, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 34, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 35, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 36, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 37, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 38, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
{.sector = 39, .key_a = 0xa229e68ad9e5, .key_b = 0x49c2b5296ef4},
};
bool social_card_moscow_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
furi_assert(nfc_worker);
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
FURI_LOG_I("Social card moscow", "Card type mismatch");
return false;
}
uint8_t sector = 15;
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
FURI_LOG_D("Social card moscow", "Verifying sector %d", sector);
if(mf_classic_authenticate(tx_rx, block, 0xa0a1a2a3a4a5, MfClassicKeyA)) {
FURI_LOG_D("Social card moscow", "Sector %d verified", sector);
return true;
}
FURI_LOG_I("Social card moscow", "Not verified");
return false;
}
bool social_card_moscow_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
furi_assert(nfc_worker);
MfClassicReader reader = {};
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
for(size_t i = 0; i < COUNT_OF(social_card_moscow_keys); i++) {
mf_classic_reader_add_sector(
&reader,
social_card_moscow_keys[i].sector,
social_card_moscow_keys[i].key_a,
social_card_moscow_keys[i].key_b);
}
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40;
}
bool social_card_moscow_parser_parse(NfcDeviceData* dev_data) {
MfClassicData* data = &dev_data->mf_classic_data;
// Verify key
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 15);
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
if(key != social_card_moscow_keys[15].key_a) return false;
// Verify card type
if(data->type != MfClassicType4k) return false;
//Print social number
uint32_t card_code = bit_lib_get_bits_32(data->block[60].value, 8, 24);
uint8_t card_region = bit_lib_get_bits(data->block[60].value, 32, 8);
uint64_t card_number = bit_lib_get_bits_64(data->block[60].value, 40, 40);
uint8_t card_control = bit_lib_get_bits(data->block[60].value, 80, 4);
uint64_t omc_number = bit_lib_get_bits_64(data->block[21].value, 8, 64);
uint8_t year = data->block[60].value[11];
uint8_t month = data->block[60].value[12];
FuriString* metro_result = furi_string_alloc();
FuriString* ground_result = furi_string_alloc();
parse_transport_block(&data->block[4], metro_result);
parse_transport_block(&data->block[16], ground_result);
furi_string_printf(
dev_data->parsed_data,
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n%s\n%s",
card_code,
card_region,
card_number,
card_control,
omc_number,
month,
year,
data->block[60].value[13],
data->block[60].value[14],
furi_string_get_cstr(metro_result),
furi_string_get_cstr(ground_result));
furi_string_free(metro_result);
furi_string_free(ground_result);
return true;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include "nfc_supported_card.h"
bool social_card_moscow_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
bool social_card_moscow_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
bool social_card_moscow_parser_parse(NfcDeviceData* dev_data);

View file

@ -1,106 +0,0 @@
#include "nfc_supported_card.h"
#include <gui/modules/widget.h>
#include <nfc_worker_i.h>
static const MfClassicAuthContext troika_4k_keys[] = {
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
{.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
{.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698},
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb},
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
{.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
{.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b},
{.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0},
{.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
{.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408},
{.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df},
{.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9},
{.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4},
{.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216},
{.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba},
{.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252},
{.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01},
{.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994},
{.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6},
{.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32},
{.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3},
{.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5},
{.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a},
{.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80},
{.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d},
{.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2},
{.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324},
{.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae},
{.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda},
{.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d},
{.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465},
{.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4},
{.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70},
};
bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
furi_assert(nfc_worker);
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
return false;
}
uint8_t sector = 11;
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
FURI_LOG_D("Troika", "Verifying sector %d", sector);
if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) {
FURI_LOG_D("Troika", "Sector %d verified", sector);
return true;
}
return false;
}
bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
furi_assert(nfc_worker);
MfClassicReader reader = {};
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) {
mf_classic_reader_add_sector(
&reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b);
}
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40;
}
bool troika_4k_parser_parse(NfcDeviceData* dev_data) {
MfClassicData* data = &dev_data->mf_classic_data;
// Verify key
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
if(key != troika_4k_keys[4].key_a) return false;
// Verify card type
if(data->type != MfClassicType4k) return false;
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
temp_ptr = &data->block[8 * 4].value[2];
uint32_t number = 0;
for(size_t i = 1; i < 5; i++) {
number <<= 8;
number |= temp_ptr[i];
}
number >>= 4;
number |= (temp_ptr[0] & 0xf) << 28;
furi_string_printf(
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
return true;
}

View file

@ -1,9 +0,0 @@
#pragma once
#include "nfc_supported_card.h"
bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
bool troika_4k_parser_parse(NfcDeviceData* dev_data);

View file

@ -2,6 +2,9 @@
#include <gui/modules/widget.h>
#include <nfc_worker_i.h>
#include <nfc/helpers/transport.h>
#define TAG "Troyka parser"
static const MfClassicAuthContext troika_keys[] = {
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
@ -9,9 +12,9 @@ static const MfClassicAuthContext troika_keys[] = {
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
{.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698},
{.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698},
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba},
{.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb},
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
{.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
@ -20,19 +23,44 @@ static const MfClassicAuthContext troika_keys[] = {
{.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
{.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
{.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408},
{.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df},
{.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9},
{.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4},
{.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216},
{.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba},
{.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252},
{.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01},
{.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994},
{.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6},
{.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32},
{.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3},
{.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5},
{.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a},
{.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80},
{.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d},
{.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2},
{.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324},
{.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae},
{.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda},
{.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d},
{.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465},
{.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4},
{.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70},
};
bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
furi_assert(nfc_worker);
UNUSED(nfc_worker);
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) {
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k &&
nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
return false;
}
uint8_t sector = 11;
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
FURI_LOG_D("Troika", "Verifying sector %d", sector);
if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) {
if(mf_classic_authenticate(tx_rx, block, troika_keys[sector].key_a, MfClassicKeyA)) {
FURI_LOG_D("Troika", "Sector %d verified", sector);
return true;
}
@ -42,16 +70,20 @@ bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
furi_assert(nfc_worker);
MfClassicReader reader = {};
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
MfClassicData* mf_classic_data = &nfc_worker->dev_data->mf_classic_data;
MfClassicType type = mf_classic_data->type;
size_t sectors = type == MfClassicType4k ? 40 : 16;
for(size_t i = 0; i < COUNT_OF(troika_keys); i++) {
mf_classic_reader_add_sector(
&reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b);
for(size_t i = 0; i < sectors; i++) {
mf_classic_set_key_found(
mf_classic_data, troika_keys[i].sector, MfClassicKeyA, troika_keys[i].key_a);
mf_classic_set_key_found(
mf_classic_data, troika_keys[i].sector, MfClassicKeyB, troika_keys[i].key_b);
}
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
uint8_t res = mf_classic_update_card(tx_rx, mf_classic_data);
FURI_LOG_D("Troika", "Update card res: %d", res);
return res == sectors;
}
bool troika_parser_parse(NfcDeviceData* dev_data) {
@ -63,24 +95,24 @@ bool troika_parser_parse(NfcDeviceData* dev_data) {
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
if(key != troika_keys[8].key_a) break;
sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
key = nfc_util_bytes2num(sec_tr->key_a, 6);
if(key != troika_keys[4].key_a) break;
// Verify card type
if(data->type != MfClassicType1k) break;
// Parse data
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
temp_ptr = &data->block[8 * 4].value[2];
uint32_t number = 0;
for(size_t i = 1; i < 5; i++) {
number <<= 8;
number |= temp_ptr[i];
}
number >>= 4;
number |= (temp_ptr[0] & 0xf) << 28;
if(data->type != MfClassicType1k && data->type != MfClassicType4k) break;
FuriString* metro_result = furi_string_alloc();
FuriString* ground_result = furi_string_alloc();
parse_transport_block(&data->block[32], metro_result);
parse_transport_block(&data->block[28], ground_result);
furi_string_printf(
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
dev_data->parsed_data,
"\e#Troika\n%s\n%s",
furi_string_get_cstr(metro_result),
furi_string_get_cstr(ground_result));
furi_string_free(metro_result);
furi_string_free(ground_result);
troika_parsed = true;
} while(false);