mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-23 13:03:13 +00:00
Merge pull request #3388 from Sanduuz/feature/hrt_parser
Add support for parsing Finnish Helsinki Regional Transport (HRT) travel cards
This commit is contained in:
9 files changed
+2039
No files matched your search
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Add support for parsing Finnish Helsinki Regional Transport (HRT) travel cards (@sanduuz)
|
||||
- Added standalone mode `HF_DOEGOX_COMMIT`: DESFire suspended commit without relay (@doegox)
|
||||
- Added support for Innovatron protocol detection to `hf 14b info` (@kormax)
|
||||
- Improved `lf cotag reader` and `lf cotag demod`: Reimplementation and enhancement of proxmark3 COTAG support
|
||||
|
||||
@@ -483,6 +483,8 @@ set (TARGET_SOURCES
|
||||
${PM3_ROOT}/client/src/iso4217.c
|
||||
${PM3_ROOT}/client/src/jansson_path.c
|
||||
${PM3_ROOT}/client/src/lua_bitlib.c
|
||||
${PM3_ROOT}/client/src/parsers/parsehrt.c
|
||||
${PM3_ROOT}/client/src/parsers/hrtparser/hrtparser.c
|
||||
${PM3_ROOT}/client/src/pla.c
|
||||
${PM3_ROOT}/client/src/preferences.c
|
||||
${PM3_ROOT}/client/src/pm3.c
|
||||
|
||||
@@ -882,6 +882,8 @@ SRCS = mifare/aiddesfire.c \
|
||||
mifare/prime.c \
|
||||
mifare/gen4.c \
|
||||
nfc/ndef.c \
|
||||
parsers/parsehrt.c \
|
||||
parsers/hrtparser/hrtparser.c \
|
||||
pm3.c \
|
||||
pm3_binlib.c \
|
||||
pm3_bitlib.c \
|
||||
|
||||
@@ -403,6 +403,8 @@ set (TARGET_SOURCES
|
||||
${PM3_ROOT}/client/src/iso4217.c
|
||||
${PM3_ROOT}/client/src/jansson_path.c
|
||||
${PM3_ROOT}/client/src/lua_bitlib.c
|
||||
${PM3_ROOT}/client/src/parsers/parsehrt.c
|
||||
${PM3_ROOT}/client/src/parsers/hrtparser/hrtparser.c
|
||||
${PM3_ROOT}/client/src/pla.c
|
||||
${PM3_ROOT}/client/src/preferences.c
|
||||
${PM3_ROOT}/client/src/pm3.c
|
||||
|
||||
@@ -26,12 +26,15 @@
|
||||
#include "commonutil.h" // ARRAYLEN
|
||||
#include "cmdparser.h" // command_t
|
||||
#include "comms.h"
|
||||
#include "mifare/desfirecrypto.h"
|
||||
#include "pm3_cmd.h"
|
||||
#include "ui.h"
|
||||
#include "cmdhf14a.h"
|
||||
#include "aes.h"
|
||||
#include "crypto/libpcrypto.h"
|
||||
#include "crypto/duoxcrypto.h"
|
||||
#include "protocols.h"
|
||||
#include "parsers/parsehrt.h"
|
||||
#include "cmdtrace.h"
|
||||
#include "cliparser.h"
|
||||
#include "iso7816/apduinfo.h" // APDU manipulation / errorcodes
|
||||
@@ -1129,6 +1132,10 @@ static int CmdHF14ADesInfo(const char *Cmd) {
|
||||
|
||||
*/
|
||||
|
||||
if (is_valid_hrt_card(&dctx, aidbuf, aidbuflen)) {
|
||||
(void)hrt_parser_parse(&dctx);
|
||||
}
|
||||
|
||||
DropField();
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,306 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Parse Helsinki Region Transport (HRT) travel cards
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef HRTPARSER_H__
|
||||
#define HRTPARSER_H__
|
||||
|
||||
#include "common.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#define HRT_TRAVELCARD_OK_STATUS 0
|
||||
#define HRT_TRAVELCARD_NO_HSL_CARD 1
|
||||
#define HRT_TRAVELCARD_HSL_CARD_DATA_FAILURE 2
|
||||
#define HRT_TRAVELCARD_CARD_READ_FAILURE 3
|
||||
#define HRT_TRAVELCARD_HSL_CARDNUMBER_FAILURE 4
|
||||
|
||||
#define MSB(b) ((b) & 0x80)
|
||||
#define LSB7(b) ((b) & 0x7F)
|
||||
|
||||
typedef struct {
|
||||
int boarding_area;
|
||||
time_t boarding_date;
|
||||
int boarding_direction;
|
||||
int boarding_location_num;
|
||||
int boarding_location_num_type;
|
||||
int boarding_vehicle;
|
||||
int child;
|
||||
int ext1_fare;
|
||||
int ext1_validity_area;
|
||||
int ext2_fare;
|
||||
int ext2_validity_area;
|
||||
int ext_period_pass_validity_area;
|
||||
int ext_product_code;
|
||||
int extra_zone;
|
||||
int group_size;
|
||||
int language_code;
|
||||
int product_code;
|
||||
int product_code_group;
|
||||
time_t sale_date;
|
||||
int sale_status;
|
||||
int sale_time;
|
||||
int ticket_fare;
|
||||
int ticket_fare_group;
|
||||
int validity_area;
|
||||
int validity_area_type;
|
||||
time_t validity_end_date;
|
||||
time_t validity_end_date_group;
|
||||
bool has_validity_end_date_group;
|
||||
int validity_length;
|
||||
int validity_length_group;
|
||||
int validity_length_type;
|
||||
int validity_length_type_group;
|
||||
time_t validity_start_date;
|
||||
int validity_status;
|
||||
} hrt_eticket_t;
|
||||
|
||||
typedef struct {
|
||||
int group_size;
|
||||
int price;
|
||||
time_t transaction_d_time;
|
||||
int transaction_type;
|
||||
time_t transfer_end_date;
|
||||
} PACKED hrt_history_t;
|
||||
|
||||
typedef struct {
|
||||
// ---- Status ----
|
||||
int app_status;
|
||||
int error_status;
|
||||
|
||||
// ---- Application Information ----
|
||||
uint8_t *application_information_data;
|
||||
uint8_t *application_information_data_v2;
|
||||
char *application_instance_id;
|
||||
int application_key_version;
|
||||
int application_version;
|
||||
int version;
|
||||
int security_level;
|
||||
int platform_type;
|
||||
|
||||
// ---- Boarding Information ----
|
||||
int boarding_area;
|
||||
int boarding_area_type;
|
||||
time_t boarding_date;
|
||||
int boarding_direction;
|
||||
int boarding_location_num;
|
||||
int boarding_location_num_type;
|
||||
int boarding_vehicle;
|
||||
time_t last_boarding_date_time;
|
||||
|
||||
// ---- Control / Ticket data ----
|
||||
uint8_t *control_information_data;
|
||||
uint8_t *control_information_data_v2;
|
||||
uint8_t *eticket_data;
|
||||
uint8_t *eticket_data_v2;
|
||||
|
||||
// ---- History ----
|
||||
uint8_t *history_data;
|
||||
uint8_t *history_data_v2;
|
||||
hrt_history_t *history_fields;
|
||||
int history_len;
|
||||
|
||||
// ---- Period Pass data ----
|
||||
uint8_t *period_pass_data;
|
||||
uint8_t *period_pass_data_v2;
|
||||
|
||||
time_t period_loading_date;
|
||||
int period_loading_device_number;
|
||||
int period_loading_organization;
|
||||
|
||||
time_t period_start_date1;
|
||||
time_t period_end_date1;
|
||||
int period_length1;
|
||||
int product_code1;
|
||||
int product_code_type1;
|
||||
int validity_area1;
|
||||
int validity_area_type1;
|
||||
|
||||
time_t period_start_date2;
|
||||
time_t period_end_date2;
|
||||
int period_length2;
|
||||
int product_code2;
|
||||
int product_code_type2;
|
||||
int validity_area2;
|
||||
int validity_area_type2;
|
||||
|
||||
int loaded_period_length;
|
||||
int loaded_period_price;
|
||||
int loaded_period_product;
|
||||
int loaded_period_product_type;
|
||||
|
||||
// ---- Stored Value ----
|
||||
uint8_t *stored_value_data;
|
||||
uint8_t *stored_value_data_v2;
|
||||
int stored_value_counter;
|
||||
|
||||
// ---- Value Ticket ----
|
||||
hrt_eticket_t *value_ticket;
|
||||
} PACKED hrt_travel_card_t;
|
||||
|
||||
int hrt_price_to_string(int cents, char *out, size_t out_len);
|
||||
char *convert_get_hex_string(const uint8_t *data, size_t length);
|
||||
int convert_get_byte_value(
|
||||
const uint8_t *data,
|
||||
size_t data_len,
|
||||
int bit_offset,
|
||||
int bit_length
|
||||
);
|
||||
int convert_get_int_value(
|
||||
const uint8_t *data,
|
||||
size_t data_len,
|
||||
int bit_offset,
|
||||
int bit_length
|
||||
);
|
||||
int convert_get_short_value(
|
||||
const uint8_t *data,
|
||||
size_t data_len,
|
||||
int bit_offset,
|
||||
int bit_length
|
||||
);
|
||||
|
||||
time_t en5145_date_to_time(int days);
|
||||
time_t en5145_datetime_to_time(int days, int minutes);
|
||||
|
||||
void hrt_travelcard_init_from_buffers(
|
||||
hrt_travel_card_t *card,
|
||||
const uint8_t *bArr,
|
||||
const uint8_t *bArr2,
|
||||
const uint8_t *bArr3,
|
||||
const uint8_t *bArr4,
|
||||
const uint8_t *bArr5,
|
||||
const uint8_t *bArr6,
|
||||
size_t history_len,
|
||||
int version
|
||||
);
|
||||
|
||||
void hrt_travelcard_init_with_error(
|
||||
hrt_travel_card_t *card,
|
||||
int error_status
|
||||
);
|
||||
|
||||
void hrt_travelcard_free(hrt_travel_card_t *card);
|
||||
|
||||
void hrt_history_init(hrt_history_t *history);
|
||||
void hrt_history_set_transaction_datetime(hrt_history_t *history, time_t datetime);
|
||||
void hrt_history_set_transaction_type(hrt_history_t *history, int type);
|
||||
void hrt_history_set_group_size(hrt_history_t *history, int group_size);
|
||||
void hrt_history_set_price(hrt_history_t *history, int price);
|
||||
void hrt_history_set_transfer_end_date(hrt_history_t *history, time_t datetime);
|
||||
|
||||
// E-ticket Functions
|
||||
bool hrt_eticket_is_defined(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_product_code(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_validity_length_type(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_validity_length(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_validity_area_type(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_validity_area(const hrt_eticket_t *ticket);
|
||||
time_t hrt_eticket_get_sale_date(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_sale_time(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ticket_fare(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_right_fare(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_total_fare(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_group_size(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_sale_status(const hrt_eticket_t *ticket);
|
||||
time_t hrt_eticket_get_validity_start_date(const hrt_eticket_t *ticket);
|
||||
time_t hrt_eticket_get_validity_end_date(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_validity_status(const hrt_eticket_t *ticket);
|
||||
time_t hrt_eticket_get_boarding_date(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_boarding_vehicle(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_boarding_location_num_type(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_boarding_location_num(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_boarding_direction(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_boarding_area(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_extra_zone(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ext_period_pass_validity_area(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ext_product_code(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ext1_validity_area(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ext1_fare(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ext2_validity_area(const hrt_eticket_t *ticket);
|
||||
int hrt_eticket_get_ext2_fare(const hrt_eticket_t *ticket);
|
||||
|
||||
// Parsing Functions
|
||||
void hrt_read_application_info(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
void hrt_read_control_info(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
void hrt_read_period_pass(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
void hrt_read_period_pass_v2(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
void hrt_read_stored_value(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
void hrt_read_history(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
void hrt_read_history_v2(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
|
||||
|
||||
// Getter Functions
|
||||
int hrt_travelcard_get_application_version(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_application_key_version(const hrt_travel_card_t *card);
|
||||
const char *hrt_travelcard_get_application_instance_id(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_platform_type(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_security_level(const hrt_travel_card_t *card);
|
||||
|
||||
int hrt_travelcard_get_product_code_type1(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_product_code1(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_validity_area_type1(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_validity_area1(const hrt_travel_card_t *card);
|
||||
time_t hrt_travelcard_get_period_start_date1(const hrt_travel_card_t *card);
|
||||
time_t hrt_travelcard_get_period_end_date1(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_period_length1(const hrt_travel_card_t *card);
|
||||
|
||||
int hrt_travelcard_get_product_code_type2(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_product_code2(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_validity_area_type2(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_validity_area2(const hrt_travel_card_t *card);
|
||||
time_t hrt_travelcard_get_period_start_date2(const hrt_travel_card_t *card);
|
||||
time_t hrt_travelcard_get_period_end_date2(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_period_length2(const hrt_travel_card_t *card);
|
||||
|
||||
int hrt_travelcard_get_stored_value_counter(const hrt_travel_card_t *card);
|
||||
|
||||
const hrt_eticket_t *hrt_travelcard_get_value_ticket(const hrt_travel_card_t *card);
|
||||
|
||||
const hrt_history_t *hrt_travelcard_get_history(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_history_len(const hrt_travel_card_t *card);
|
||||
|
||||
int hrt_travelcard_get_boarding_area(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_boarding_area_type(const hrt_travel_card_t *card);
|
||||
time_t hrt_travelcard_get_boarding_date(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_boarding_vehicle(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_boarding_location_num_type(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_boarding_location_num(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_boarding_direction(const hrt_travel_card_t *card);
|
||||
|
||||
int hrt_travelcard_get_loaded_period_product_type(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_loaded_period_product(const hrt_travel_card_t *card);
|
||||
time_t hrt_travelcard_get_period_loading_date(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_loaded_period_length(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_loaded_period_price(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_period_loading_organization(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_period_loading_device_number(const hrt_travel_card_t *card);
|
||||
|
||||
int hrt_travelcard_get_app_status(const hrt_travel_card_t *card);
|
||||
int hrt_travelcard_get_version(const hrt_travel_card_t *card);
|
||||
|
||||
int HRTParser(const char *Cmd);
|
||||
|
||||
// Setter Functions
|
||||
void hrt_travelcard_init_empty(hrt_travel_card_t *card, int version);
|
||||
bool hrt_travelcard_set_application_info(hrt_travel_card_t *card, const uint8_t *buf, size_t len);
|
||||
bool hrt_travelcard_set_control_info(hrt_travel_card_t *card, const uint8_t *buf, size_t len);
|
||||
bool hrt_travelcard_set_period_pass(hrt_travel_card_t *card, const uint8_t *buf, size_t len);
|
||||
bool hrt_travelcard_set_stored_value(hrt_travel_card_t *card, const uint8_t *buf, size_t len);
|
||||
bool hrt_travelcard_set_eticket(hrt_travel_card_t *card, const uint8_t *buf, size_t len);
|
||||
bool hrt_travelcard_set_history(hrt_travel_card_t *card, const uint8_t *buf, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,521 @@
|
||||
#include "commonutil.h"
|
||||
#include "hrtparser/hrtparser.h"
|
||||
#include "iso7816/iso7816core.h"
|
||||
#include "mifare/desfirecore.h"
|
||||
#include "mifare/desfirecrypto.h"
|
||||
#include "parsehrt.h"
|
||||
#include "pm3_cmd.h"
|
||||
#include "ui.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define HRT_NEW_AID 0xEF2014
|
||||
#define HRT_OLD_AID 0xEF2011
|
||||
#define HRT_CONTROL_INFO_FILE_ID 0x00
|
||||
#define HRT_PERIOD_PASS_FILE_ID 0x01
|
||||
#define HRT_STORED_VALUE_FILE_ID 0x02
|
||||
#define HRT_ETICKET_FILE_ID 0x03
|
||||
#define HRT_HISTORY_FILE_ID 0x04
|
||||
#define HRT_APPLICATION_INFO_FILE_ID 0x08
|
||||
#define HRT_APPLICATION_INFO_LEN 11
|
||||
#define HRT_CONTROL_INFO_LEN 6
|
||||
#define HRT_CONTROL_INFO_V2_LEN 10
|
||||
#define HRT_PERIOD_PASS_LEN 32
|
||||
#define HRT_PERIOD_PASS_V2_LEN 35
|
||||
#define HRT_STORED_VALUE_LEN 12
|
||||
#define HRT_STORED_VALUE_V2_LEN 13
|
||||
#define HRT_ETICKET_LEN 26
|
||||
#define HRT_ETICKET_V2_LEN 45
|
||||
|
||||
typedef struct {
|
||||
uint32_t aid;
|
||||
int version;
|
||||
} hrt_application_t;
|
||||
|
||||
typedef enum {
|
||||
HRT_AREA_TYPE_CITY = 0,
|
||||
HRT_AREA_TYPE_VEHICLE = 1,
|
||||
HRT_AREA_TYPE_ZONE_RANGE = 2,
|
||||
} hrt_area_type_t;
|
||||
|
||||
typedef struct {
|
||||
int code;
|
||||
const char *name;
|
||||
const char *arc_zone;
|
||||
} hrt_area_mapping_t;
|
||||
|
||||
static const hrt_area_mapping_t hrt_city_areas[] = {
|
||||
{0, "Not defined", NULL},
|
||||
{1, "Helsinki", "AB"},
|
||||
{2, "Espoo", "BC"},
|
||||
{4, "Vantaa", "BC"},
|
||||
{5, "Region", "ABC"},
|
||||
{6, "Kirkkonummi-Siuntio", "D"},
|
||||
{7, "Vihti", NULL},
|
||||
{8, "Nurmijarvi", NULL},
|
||||
{9, "Kerava-Sipoo-Tuusula", "D"},
|
||||
{10, "Sipoo", NULL},
|
||||
{14, "Region zone 2", "BCD"},
|
||||
{15, "Region zone 3", "ABCD"},
|
||||
};
|
||||
|
||||
static const hrt_area_mapping_t hrt_vehicle_areas[] = {
|
||||
{0, "Not defined", NULL},
|
||||
{1, "Bus", NULL},
|
||||
{5, "Tram", NULL},
|
||||
{6, "Metro", NULL},
|
||||
{7, "Train", NULL},
|
||||
{8, "Ferry", NULL},
|
||||
{9, "U line", NULL},
|
||||
};
|
||||
|
||||
static const char *hrt_lookup_product_name(int product_code) {
|
||||
if (product_code < 800) return "Not defined";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const hrt_area_mapping_t *hrt_lookup_area_mapping(const hrt_area_mapping_t *mappings, size_t mappings_len, int area) {
|
||||
for (size_t i = 0; i < mappings_len; i++) {
|
||||
if (mappings[i].code == area) {
|
||||
return &mappings[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool hrt_format_zone_range(int area, char *out, size_t out_len) {
|
||||
static const char zones[] = "ABCDEFGH";
|
||||
if (out == NULL || out_len == 0) return false;
|
||||
|
||||
int end = area & 0x07;
|
||||
int start = (area >> 3) & 0x07;
|
||||
|
||||
if (start > end || start >= (int)strlen(zones) || end >= (int)strlen(zones)) return false;
|
||||
|
||||
size_t pos = 0;
|
||||
for (int i = start; i <= end; i++) {
|
||||
if (pos + 1 >= out_len) return false;
|
||||
out[pos++] = zones[i];
|
||||
}
|
||||
|
||||
out[pos] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool hrt_format_area(int area_type, int area, char *out, size_t out_len) {
|
||||
if (out == NULL || out_len == 0) return false;
|
||||
|
||||
const hrt_area_mapping_t *mapping = NULL;
|
||||
switch ((hrt_area_type_t)area_type) {
|
||||
case HRT_AREA_TYPE_CITY:
|
||||
mapping = hrt_lookup_area_mapping(hrt_city_areas, ARRAYLEN(hrt_city_areas), area);
|
||||
if (mapping == NULL) return false;
|
||||
if (mapping->arc_zone != NULL) {
|
||||
return snprintf(out, out_len, "%s (%s)", mapping->name, mapping->arc_zone) > 0;
|
||||
}
|
||||
return snprintf(out, out_len, "%s", mapping->name) > 0;
|
||||
case HRT_AREA_TYPE_VEHICLE:
|
||||
mapping = hrt_lookup_area_mapping(hrt_vehicle_areas, ARRAYLEN(hrt_vehicle_areas), area);
|
||||
if (mapping == NULL) return false;
|
||||
return snprintf(out, out_len, "%s", mapping->name) > 0;
|
||||
case HRT_AREA_TYPE_ZONE_RANGE:
|
||||
return hrt_format_zone_range(area, out, out_len);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool hrt_format_card_number(const char *card_number, char *out, size_t out_len) {
|
||||
if (card_number == NULL || out == NULL || out_len == 0) return false;
|
||||
|
||||
// Card number is printed on the physical card in groups of 6 + 4 + 4 + 4.
|
||||
if (strlen(card_number) != 18 || out_len < 22) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int written = snprintf(out, out_len, "%.6s %.4s %.4s %.4s",
|
||||
card_number, card_number + 6,
|
||||
card_number + 10, card_number + 14);
|
||||
|
||||
return written > 0 && (size_t)written < out_len;
|
||||
}
|
||||
|
||||
static bool hrt_get_application_from_aid_list(const uint8_t *aidbuf, size_t aidbuflen, hrt_application_t *application) {
|
||||
if (aidbuf == NULL || aidbuflen < 3) return false;
|
||||
|
||||
for (size_t i = 0; i + 2 < aidbuflen; i += 3) {
|
||||
uint32_t aid = DesfireAIDByteToUint(&aidbuf[i]);
|
||||
|
||||
if (aid == HRT_OLD_AID) {
|
||||
if (application != NULL) {
|
||||
application->aid = HRT_OLD_AID;
|
||||
application->version = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aid == HRT_NEW_AID) {
|
||||
if (application != NULL) {
|
||||
application->aid = HRT_NEW_AID;
|
||||
application->version = 2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t hrt_control_info_len(int version) {
|
||||
return version == 2 ? HRT_CONTROL_INFO_V2_LEN : HRT_CONTROL_INFO_LEN;
|
||||
}
|
||||
|
||||
static size_t hrt_period_pass_len(int version) {
|
||||
return version == 2 ? HRT_PERIOD_PASS_V2_LEN : HRT_PERIOD_PASS_LEN;
|
||||
}
|
||||
|
||||
static size_t hrt_stored_value_len(int version) {
|
||||
return version == 2 ? HRT_STORED_VALUE_V2_LEN : HRT_STORED_VALUE_LEN;
|
||||
}
|
||||
|
||||
static size_t hrt_eticket_len(int version) {
|
||||
return version == 2 ? HRT_ETICKET_V2_LEN : HRT_ETICKET_LEN;
|
||||
}
|
||||
|
||||
static bool hrt_read_and_parse_application_info(DesfireContext_t *dctx, hrt_travel_card_t *card) {
|
||||
if (dctx == NULL || card == NULL) return false;
|
||||
|
||||
uint8_t buf[APDU_RES_LEN] = {0};
|
||||
size_t len = 0;
|
||||
|
||||
if (DesfireReadFile(dctx, HRT_APPLICATION_INFO_FILE_ID, 0, HRT_APPLICATION_INFO_LEN, buf, &len) != PM3_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hrt_travelcard_set_application_info(card, buf, len) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *application_id = hrt_travelcard_get_application_instance_id(card);
|
||||
return application_id != NULL && application_id[0] != '\0';
|
||||
}
|
||||
|
||||
static bool hrt_format_time(time_t value, char *out, size_t out_len) {
|
||||
if (value == (time_t)0 || out == NULL || out_len == 0) return false;
|
||||
|
||||
char buf[32] = {0};
|
||||
struct tm tm_value = {0};
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (localtime_s(&tm_value, &value) != 0) return false;
|
||||
#else
|
||||
if (localtime_r(&value, &tm_value) == NULL) return false;
|
||||
#endif
|
||||
|
||||
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_value) == 0) return false;
|
||||
return snprintf(out, out_len, "%s", buf) > 0;
|
||||
}
|
||||
|
||||
static void hrt_print_time(const char *label, time_t value) {
|
||||
char buf[32] = {0};
|
||||
if (hrt_format_time(value, buf, sizeof(buf)) == false) return;
|
||||
|
||||
PrintAndLogEx(SUCCESS, "%s " _GREEN_("%s"), label, buf);
|
||||
}
|
||||
|
||||
static bool hrt_has_loaded_period_info(const hrt_travel_card_t *card) {
|
||||
if (card == NULL) return false;
|
||||
|
||||
return hrt_travelcard_get_loaded_period_product(card) > 0 ||
|
||||
hrt_travelcard_get_loaded_period_length(card) > 0 ||
|
||||
hrt_travelcard_get_loaded_period_price(card) > 0 ||
|
||||
hrt_travelcard_get_period_loading_date(card) != (time_t)0 ||
|
||||
hrt_travelcard_get_period_loading_organization(card) > 0 ||
|
||||
hrt_travelcard_get_period_loading_device_number(card) > 0;
|
||||
}
|
||||
|
||||
static void hrt_print_boarding_location(int location_type, int location_num) {
|
||||
if (location_num <= 0) return;
|
||||
|
||||
if (location_type == 1) {
|
||||
PrintAndLogEx(SUCCESS, " Boarding line.......... " _GREEN_("%d"), location_num);
|
||||
} else if (location_type == 2) {
|
||||
PrintAndLogEx(SUCCESS, " Boarding train......... " _GREEN_("%d"), location_num);
|
||||
}
|
||||
}
|
||||
|
||||
static bool hrt_format_value_ticket_zone(const hrt_eticket_t *ticket, char *out, size_t out_len) {
|
||||
if (ticket == NULL || out == NULL || out_len == 0) return false;
|
||||
|
||||
if (hrt_eticket_get_extra_zone(ticket) == 1 && hrt_eticket_get_group_size(ticket) <= 1) {
|
||||
char ext1[32] = {0};
|
||||
char ext2[32] = {0};
|
||||
bool has_ext1 = false;
|
||||
bool has_ext2 = false;
|
||||
int ext1_area = hrt_eticket_get_ext1_validity_area(ticket);
|
||||
int ext2_area = hrt_eticket_get_ext2_validity_area(ticket);
|
||||
|
||||
if (ext1_area != 56) {
|
||||
has_ext1 = hrt_format_area(HRT_AREA_TYPE_ZONE_RANGE, ext1_area, ext1, sizeof(ext1));
|
||||
}
|
||||
if (ext2_area != 56) {
|
||||
has_ext2 = hrt_format_area(HRT_AREA_TYPE_ZONE_RANGE, ext2_area, ext2, sizeof(ext2));
|
||||
}
|
||||
|
||||
if (has_ext1 && has_ext2) {
|
||||
return snprintf(out, out_len, "%s+%s", ext1, ext2) > 0;
|
||||
}
|
||||
if (has_ext1) return snprintf(out, out_len, "%s", ext1) > 0;
|
||||
if (has_ext2) return snprintf(out, out_len, "%s", ext2) > 0;
|
||||
}
|
||||
|
||||
return hrt_format_area(hrt_eticket_get_validity_area_type(ticket),
|
||||
hrt_eticket_get_validity_area(ticket), out, out_len);
|
||||
}
|
||||
|
||||
static const char *hrt_history_transaction_type_name(int transaction_type) {
|
||||
return transaction_type == 0 ? "Season ticket" : "Value ticket";
|
||||
}
|
||||
|
||||
static void hrt_print_history(const hrt_travel_card_t *card) {
|
||||
if (card == NULL) return;
|
||||
|
||||
const hrt_history_t *history = hrt_travelcard_get_history(card);
|
||||
int history_len = hrt_travelcard_get_history_len(card);
|
||||
if (history == NULL || history_len <= 0) return;
|
||||
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "History records");
|
||||
|
||||
for (int i = history_len - 1; i >= 0; i--) {
|
||||
char transaction_time[32] = {0};
|
||||
char price_buf[32] = {0};
|
||||
|
||||
if (hrt_format_time(history[i].transaction_d_time, transaction_time, sizeof(transaction_time)) == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, " %s " _GREEN_("%s"), transaction_time,
|
||||
hrt_history_transaction_type_name(history[i].transaction_type));
|
||||
|
||||
if (history[i].transaction_type != 0 &&
|
||||
(history[i].group_size > 1 || history[i].price > 0)) {
|
||||
if (history[i].group_size > 1) {
|
||||
PrintAndLogEx(SUCCESS, " Group size.......... " _GREEN_("%d"), history[i].group_size);
|
||||
}
|
||||
if (history[i].price > 0) {
|
||||
hrt_price_to_string(history[i].price, price_buf, sizeof(price_buf));
|
||||
PrintAndLogEx(SUCCESS, " Price............... " _GREEN_("%s"), price_buf);
|
||||
}
|
||||
}
|
||||
|
||||
hrt_print_time(" Transfer ends.......", history[i].transfer_end_date);
|
||||
}
|
||||
}
|
||||
|
||||
static void hrt_print_card(const hrt_travel_card_t *card) {
|
||||
if (card == NULL) return;
|
||||
|
||||
const char *application_id = hrt_travelcard_get_application_instance_id(card);
|
||||
char price_buf[32] = {0};
|
||||
char area_buf[64] = {0};
|
||||
int product_code = hrt_travelcard_get_product_code1(card);
|
||||
const char *product_name = hrt_lookup_product_name(product_code);
|
||||
int area_type = hrt_travelcard_get_validity_area_type1(card);
|
||||
int area = hrt_travelcard_get_validity_area1(card);
|
||||
char boarding_area_buf[64] = {0};
|
||||
const hrt_eticket_t *value_ticket = hrt_travelcard_get_value_ticket(card);
|
||||
|
||||
hrt_price_to_string(
|
||||
hrt_travelcard_get_stored_value_counter(card),
|
||||
price_buf,
|
||||
sizeof(price_buf)
|
||||
);
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "--- " _CYAN_("HRT travel card"));
|
||||
|
||||
if (application_id != NULL && application_id[0] != '\0') {
|
||||
char card_number[22] = {0};
|
||||
if (hrt_format_card_number(application_id, card_number, sizeof(card_number))) {
|
||||
PrintAndLogEx(SUCCESS, "Card number......... " _GREEN_("%s"), card_number);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, "Card number......... " _GREEN_("%s"), application_id);
|
||||
}
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, "Value on the card... " _GREEN_("%s"), price_buf);
|
||||
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "Season ticket information");
|
||||
|
||||
if (product_name != NULL) {
|
||||
PrintAndLogEx(SUCCESS, " Product code........... " _GREEN_("%d (%s)"), product_code, product_name);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, " Product code........... " _GREEN_("%d (unknown)"), product_code);
|
||||
}
|
||||
if (hrt_format_area(area_type, area, area_buf, sizeof(area_buf))) {
|
||||
PrintAndLogEx(SUCCESS, " Area................... " _GREEN_("%s"), area_buf);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, " Area................... " _GREEN_("%d (type %d)"), area, area_type);
|
||||
}
|
||||
hrt_print_time(" Start date.............", hrt_travelcard_get_period_start_date1(card));
|
||||
hrt_print_time(" End date...............", hrt_travelcard_get_period_end_date1(card));
|
||||
PrintAndLogEx(SUCCESS, " Length................. " _GREEN_("%d days"), hrt_travelcard_get_period_length1(card));
|
||||
hrt_print_time(" Boarding time..........", hrt_travelcard_get_boarding_date(card));
|
||||
hrt_print_boarding_location(hrt_travelcard_get_boarding_location_num_type(card),
|
||||
hrt_travelcard_get_boarding_location_num(card));
|
||||
|
||||
if (hrt_format_area(hrt_travelcard_get_boarding_area_type(card),
|
||||
hrt_travelcard_get_boarding_area(card),
|
||||
boarding_area_buf, sizeof(boarding_area_buf))) {
|
||||
PrintAndLogEx(SUCCESS, " Boarding zone.......... " _GREEN_("%s"), boarding_area_buf);
|
||||
}
|
||||
|
||||
if (hrt_has_loaded_period_info(card)) {
|
||||
PrintAndLogEx(SUCCESS, " Organization........... " _GREEN_("%d"), hrt_travelcard_get_period_loading_organization(card));
|
||||
PrintAndLogEx(SUCCESS, " Device number.......... " _GREEN_("%d"), hrt_travelcard_get_period_loading_device_number(card));
|
||||
}
|
||||
|
||||
if (hrt_eticket_is_defined(value_ticket)) {
|
||||
char fare_buf[32] = {0};
|
||||
char value_area_buf[64] = {0};
|
||||
int value_product_code = hrt_eticket_get_product_code(value_ticket);
|
||||
const char *value_product_name = hrt_lookup_product_name(value_product_code);
|
||||
|
||||
hrt_price_to_string(hrt_eticket_get_total_fare(value_ticket), fare_buf, sizeof(fare_buf));
|
||||
|
||||
PrintAndLogEx(SUCCESS, "");
|
||||
PrintAndLogEx(SUCCESS, "Value ticket information");
|
||||
|
||||
if (value_product_name != NULL) {
|
||||
PrintAndLogEx(SUCCESS, " Product code........... " _GREEN_("%d (%s)"), value_product_code, value_product_name);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, " Product code........... " _GREEN_("%d (unknown)"), value_product_code);
|
||||
}
|
||||
|
||||
if (hrt_format_value_ticket_zone(value_ticket, value_area_buf, sizeof(value_area_buf))) {
|
||||
PrintAndLogEx(SUCCESS, " Zone................... " _GREEN_("%s"), value_area_buf);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, " Zone................... " _GREEN_("unknown"));
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, " Fare................... " _GREEN_("%s"), fare_buf);
|
||||
PrintAndLogEx(SUCCESS, " Group size............. " _GREEN_("%d"), hrt_eticket_get_group_size(value_ticket));
|
||||
hrt_print_time(" Valid from.............", hrt_eticket_get_validity_start_date(value_ticket));
|
||||
hrt_print_time(" Valid until............", hrt_eticket_get_validity_end_date(value_ticket));
|
||||
hrt_print_time(" Boarding time..........", hrt_eticket_get_boarding_date(value_ticket));
|
||||
PrintAndLogEx(SUCCESS, " Boarding vehicle....... " _GREEN_("%d"), hrt_eticket_get_boarding_vehicle(value_ticket));
|
||||
hrt_print_boarding_location(hrt_eticket_get_boarding_location_num_type(value_ticket),
|
||||
hrt_eticket_get_boarding_location_num(value_ticket));
|
||||
}
|
||||
|
||||
hrt_print_history(card);
|
||||
}
|
||||
|
||||
static bool hrt_select_application(DesfireContext_t *dctx, const hrt_application_t *application) {
|
||||
if (dctx == NULL || application == NULL) return false;
|
||||
|
||||
return DesfireSelectAIDHex(dctx, application->aid, false, 0) == PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static bool hrt_read_card(DesfireContext_t *dctx, const hrt_application_t *application, hrt_travel_card_t *card) {
|
||||
if (dctx == NULL || application == NULL || card == NULL) return false;
|
||||
|
||||
uint8_t buf[APDU_RES_LEN] = {0};
|
||||
size_t len = 0;
|
||||
|
||||
hrt_travelcard_init_empty(card, application->version);
|
||||
|
||||
// File 0x08: Application info
|
||||
if (hrt_read_and_parse_application_info(dctx, card) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// File 0x00: Control info
|
||||
len = 0;
|
||||
if (DesfireReadFile(dctx, HRT_CONTROL_INFO_FILE_ID, 0, hrt_control_info_len(application->version), buf, &len) == PM3_SUCCESS) {
|
||||
hrt_travelcard_set_control_info(card, buf, len);
|
||||
}
|
||||
|
||||
// File 0x01: Period pass
|
||||
len = 0;
|
||||
if (DesfireReadFile(dctx, HRT_PERIOD_PASS_FILE_ID, 0, hrt_period_pass_len(application->version), buf, &len) == PM3_SUCCESS) {
|
||||
hrt_travelcard_set_period_pass(card, buf, len);
|
||||
}
|
||||
|
||||
// File 0x02: Stored value
|
||||
len = 0;
|
||||
if (DesfireReadFile(dctx, HRT_STORED_VALUE_FILE_ID, 0, hrt_stored_value_len(application->version), buf, &len) == PM3_SUCCESS) {
|
||||
hrt_travelcard_set_stored_value(card, buf, len);
|
||||
}
|
||||
|
||||
// File 0x03: eTicket
|
||||
len = 0;
|
||||
if (DesfireReadFile(dctx, HRT_ETICKET_FILE_ID, 0, hrt_eticket_len(application->version), buf, &len) == PM3_SUCCESS) {
|
||||
hrt_travelcard_set_eticket(card, buf, len);
|
||||
}
|
||||
|
||||
// File 0x04: History records
|
||||
len = 0;
|
||||
if (DesfireReadRecords(dctx, HRT_HISTORY_FILE_ID, 0, 0, buf, &len) == PM3_SUCCESS) {
|
||||
hrt_travelcard_set_history(card, buf, len);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_valid_hrt_card(DesfireContext_t *dctx, const uint8_t *aidbuf, size_t aidbuflen) {
|
||||
if (dctx == NULL) return false;
|
||||
|
||||
hrt_application_t application = {0};
|
||||
if (hrt_get_application_from_aid_list(aidbuf, aidbuflen, &application) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DesfireSetCommMode(dctx, DCMPlain);
|
||||
DesfireSetCommandSet(dctx, DCCNative);
|
||||
|
||||
if (hrt_select_application(dctx, &application) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hrt_travel_card_t card;
|
||||
hrt_travelcard_init_empty(&card, application.version);
|
||||
|
||||
bool is_valid = hrt_read_and_parse_application_info(dctx, &card);
|
||||
|
||||
hrt_travelcard_free(&card);
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
bool hrt_parser_parse(DesfireContext_t *dctx) {
|
||||
if (dctx == NULL) return false;
|
||||
|
||||
DesfireSetCommMode(dctx, DCMPlain);
|
||||
DesfireSetCommandSet(dctx, DCCNative);
|
||||
|
||||
hrt_application_t applications[] = {
|
||||
{HRT_OLD_AID, 1},
|
||||
{HRT_NEW_AID, 2},
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ARRAYLEN(applications); i++) {
|
||||
if (hrt_select_application(dctx, &applications[i]) == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hrt_travel_card_t card;
|
||||
if (hrt_read_card(dctx, &applications[i], &card) == false) {
|
||||
hrt_travelcard_free(&card);
|
||||
continue;
|
||||
}
|
||||
|
||||
hrt_print_card(&card);
|
||||
|
||||
hrt_travelcard_free(&card);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef PARSEHRT_H__
|
||||
#define PARSEHRT_H__
|
||||
|
||||
#include "mifare/desfirecore.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool is_valid_hrt_card(DesfireContext_t *dctx, const uint8_t *aidbuf, size_t aidbuflen);
|
||||
bool hrt_parser_parse(DesfireContext_t *dctx);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user