Skip to content

Commit d747d5c

Browse files
committed
Added support for parsing and printing history records
1 parent 36f55d3 commit d747d5c

3 files changed

Lines changed: 202 additions & 16 deletions

File tree

client/src/parsers/hrtparser/hrtparser.c

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ static const int64_t MINUTE_IN_MS = 60000LL;
4949
#define HRT_ETICKET_LEN 26
5050
#define HRT_ETICKET_V2_LEN 45
5151
#define HRT_HISTORY_LEN 96
52+
#define HRT_HISTORY_RECORD_LEN 12
5253
#define HRT_HISTORY_RECORDS 8
5354

5455
int hrt_price_to_string(int cents, char *out, size_t out_len) {
@@ -669,8 +670,95 @@ void hrt_read_stored_value(hrt_travel_card_t *card, const uint8_t *data, size_t
669670
((uint32_t)data[1] << 4);
670671
}
671672

672-
// void readHistory(const uint8_t *data, size_t length);
673-
// void readHistory_v2(const uint8_t *data, size_t length);
673+
void hrt_read_history(hrt_travel_card_t *card, const uint8_t *data, size_t data_len) {
674+
if (!card || !data || !card->history_fields) return;
675+
676+
size_t record_count = data_len / HRT_HISTORY_RECORD_LEN;
677+
if (record_count > HRT_HISTORY_RECORDS) {
678+
record_count = HRT_HISTORY_RECORDS;
679+
}
680+
681+
memset(card->history_fields, 0, HRT_HISTORY_RECORDS * sizeof(hrt_history_t));
682+
card->history_len = 0;
683+
684+
for (size_t i = 0; i < record_count; i++) {
685+
size_t offset = i * HRT_HISTORY_RECORD_LEN;
686+
687+
if (data[offset + 1] == 0 &&
688+
data[offset + 2] == 0 &&
689+
data[offset + 3] == 0 &&
690+
data[offset + 4] == 0) {
691+
continue;
692+
}
693+
694+
hrt_history_t *history = &card->history_fields[card->history_len];
695+
hrt_history_init(history);
696+
697+
history->transaction_type = (data[offset] & 0x80) >> 7;
698+
699+
uint8_t date_time_byte = data[offset + 3];
700+
int days = ((date_time_byte & 0x3F) << 8) | data[offset + 4];
701+
int minutes = ((data[offset + 1] & 0x01) << 10) |
702+
((uint16_t)data[offset + 2] << 2) |
703+
((date_time_byte & 0xC0) >> 6);
704+
705+
int transfer_end_minutes = ((uint16_t)data[offset + 5] << 3) |
706+
((data[offset + 6] & 0xE0) >> 5);
707+
708+
if (transfer_end_minutes < minutes) {
709+
days--;
710+
}
711+
712+
history->transaction_d_time = en5145_datetime_to_time(days, minutes);
713+
history->price = ((uint16_t)data[offset + 7] << 1) |
714+
((data[offset + 6] & 0x1F) << 9) |
715+
((data[offset + 8] & 0x80) >> 7);
716+
history->group_size = (data[offset + 8] & 0x7C) >> 2;
717+
718+
card->history_len++;
719+
}
720+
}
721+
722+
void hrt_read_history_v2(hrt_travel_card_t *card, const uint8_t *data, size_t data_len) {
723+
if (!card || !data || !card->history_fields) return;
724+
725+
size_t record_count = data_len / HRT_HISTORY_RECORD_LEN;
726+
if (record_count > HRT_HISTORY_RECORDS) {
727+
record_count = HRT_HISTORY_RECORDS;
728+
}
729+
730+
memset(card->history_fields, 0, HRT_HISTORY_RECORDS * sizeof(hrt_history_t));
731+
card->history_len = 0;
732+
733+
for (size_t i = 0; i < record_count; i++) {
734+
size_t byte_offset = i * HRT_HISTORY_RECORD_LEN;
735+
736+
if (data[byte_offset + 1] == 0 &&
737+
data[byte_offset + 2] == 0 &&
738+
data[byte_offset + 3] == 0 &&
739+
data[byte_offset + 4] == 0) {
740+
continue;
741+
}
742+
743+
int bit_offset = (int)(i * 96);
744+
hrt_history_t *history = &card->history_fields[card->history_len];
745+
hrt_history_init(history);
746+
747+
history->transaction_type = convert_get_byte_value(data, data_len, bit_offset, 1);
748+
history->transaction_d_time = en5145_datetime_to_time(
749+
convert_get_short_value(data, data_len, bit_offset + 1, 14),
750+
convert_get_short_value(data, data_len, bit_offset + 15, 11)
751+
);
752+
history->transfer_end_date = en5145_datetime_to_time(
753+
convert_get_short_value(data, data_len, bit_offset + 26, 14),
754+
convert_get_short_value(data, data_len, bit_offset + 40, 11)
755+
);
756+
history->price = convert_get_short_value(data, data_len, bit_offset + 51, 14);
757+
history->group_size = convert_get_byte_value(data, data_len, bit_offset + 65, 6);
758+
759+
card->history_len++;
760+
}
761+
}
674762

675763
void hrt_history_init(hrt_history_t *history) {
676764
if (!history) return;
@@ -1069,14 +1157,14 @@ bool hrt_travelcard_set_history(hrt_travel_card_t *card, const uint8_t *buf, siz
10691157
if (!card->history_data_v2) return false;
10701158
if (len > HRT_HISTORY_LEN) len = HRT_HISTORY_LEN;
10711159
memcpy(card->history_data_v2, buf, len);
1072-
// TODO: readHistory_v2(card->historyData_v2, len);
1160+
hrt_read_history_v2(card, card->history_data_v2, len);
10731161
return true;
10741162
}
10751163

10761164
if (!card->history_data) return false;
10771165
if (len > HRT_HISTORY_LEN) len = HRT_HISTORY_LEN;
10781166
memcpy(card->history_data, buf, len);
1079-
// TODO: readHistory(card->historyData, len);
1167+
hrt_read_history(card, card->history_data, len);
10801168
return true;
10811169
}
10821170

client/src/parsers/hrtparser/hrtparser.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ void hrt_read_control_info(hrt_travel_card_t *card, const uint8_t *data, size_t
240240
void hrt_read_period_pass(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
241241
void hrt_read_period_pass_v2(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
242242
void hrt_read_stored_value(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
243-
void hrt_read_history(const uint8_t *data, size_t length);
244-
void hrt_read_history_v2(const uint8_t *data, size_t length);
243+
void hrt_read_history(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
244+
void hrt_read_history_v2(hrt_travel_card_t *card, const uint8_t *data, size_t data_len);
245245

246246
// Getter Functions
247247
int hrt_travelcard_get_application_version(const hrt_travel_card_t *card);

client/src/parsers/parsehrt.c

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,26 @@ static bool hrt_read_and_parse_application_info(DesfireContext_t *dctx, hrt_trav
154154
return application_id != NULL && application_id[0] != '\0';
155155
}
156156

157-
static void hrt_print_time(const char *label, time_t value) {
158-
if (value == (time_t)0) return;
157+
static bool hrt_format_time(time_t value, char *out, size_t out_len) {
158+
if (value == (time_t)0 || out == NULL || out_len == 0) return false;
159159

160160
char buf[32] = {0};
161161
struct tm tm_value = {0};
162162

163163
#if defined(_WIN32)
164-
if (localtime_s(&tm_value, &value) != 0) return;
164+
if (localtime_s(&tm_value, &value) != 0) return false;
165165
#else
166-
if (localtime_r(&value, &tm_value) == NULL) return;
166+
if (localtime_r(&value, &tm_value) == NULL) return false;
167167
#endif
168168

169-
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_value);
169+
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_value) == 0) return false;
170+
return snprintf(out, out_len, "%s", buf) > 0;
171+
}
172+
173+
static void hrt_print_time(const char *label, time_t value) {
174+
char buf[32] = {0};
175+
if (hrt_format_time(value, buf, sizeof(buf)) == false) return;
176+
170177
PrintAndLogEx(SUCCESS, "%s " _GREEN_("%s"), label, buf);
171178
}
172179

@@ -181,6 +188,85 @@ static bool hrt_has_loaded_period_info(const hrt_travel_card_t *card) {
181188
hrt_travelcard_get_period_loading_device_number(card) > 0;
182189
}
183190

191+
static void hrt_print_boarding_location(int location_type, int location_num) {
192+
if (location_num <= 0) return;
193+
194+
if (location_type == 1) {
195+
PrintAndLogEx(SUCCESS, " Boarding line.......... " _GREEN_("%d"), location_num);
196+
} else if (location_type == 2) {
197+
PrintAndLogEx(SUCCESS, " Boarding train......... " _GREEN_("%d"), location_num);
198+
}
199+
}
200+
201+
static bool hrt_format_value_ticket_zone(const hrt_eticket_t *ticket, char *out, size_t out_len) {
202+
if (ticket == NULL || out == NULL || out_len == 0) return false;
203+
204+
if (hrt_eticket_get_extra_zone(ticket) == 1 && hrt_eticket_get_group_size(ticket) <= 1) {
205+
char ext1[32] = {0};
206+
char ext2[32] = {0};
207+
bool has_ext1 = false;
208+
bool has_ext2 = false;
209+
int ext1_area = hrt_eticket_get_ext1_validity_area(ticket);
210+
int ext2_area = hrt_eticket_get_ext2_validity_area(ticket);
211+
212+
if (ext1_area != 56) {
213+
has_ext1 = hrt_format_area(HRT_AREA_TYPE_ZONE_RANGE, ext1_area, ext1, sizeof(ext1));
214+
}
215+
if (ext2_area != 56) {
216+
has_ext2 = hrt_format_area(HRT_AREA_TYPE_ZONE_RANGE, ext2_area, ext2, sizeof(ext2));
217+
}
218+
219+
if (has_ext1 && has_ext2) {
220+
return snprintf(out, out_len, "%s+%s", ext1, ext2) > 0;
221+
}
222+
if (has_ext1) return snprintf(out, out_len, "%s", ext1) > 0;
223+
if (has_ext2) return snprintf(out, out_len, "%s", ext2) > 0;
224+
}
225+
226+
return hrt_format_area(hrt_eticket_get_validity_area_type(ticket),
227+
hrt_eticket_get_validity_area(ticket), out, out_len);
228+
}
229+
230+
static const char *hrt_history_transaction_type_name(int transaction_type) {
231+
return transaction_type == 0 ? "Season ticket" : "Value ticket";
232+
}
233+
234+
static void hrt_print_history(const hrt_travel_card_t *card) {
235+
if (card == NULL) return;
236+
237+
const hrt_history_t *history = hrt_travelcard_get_history(card);
238+
int history_len = hrt_travelcard_get_history_len(card);
239+
if (history == NULL || history_len <= 0) return;
240+
241+
PrintAndLogEx(SUCCESS, "");
242+
PrintAndLogEx(SUCCESS, "History records");
243+
244+
for (int i = history_len - 1; i >= 0; i--) {
245+
char transaction_time[32] = {0};
246+
char price_buf[32] = {0};
247+
248+
if (hrt_format_time(history[i].transaction_d_time, transaction_time, sizeof(transaction_time)) == false) {
249+
continue;
250+
}
251+
252+
PrintAndLogEx(SUCCESS, " %s " _GREEN_("%s"), transaction_time,
253+
hrt_history_transaction_type_name(history[i].transaction_type));
254+
255+
if (history[i].transaction_type != 0 &&
256+
(history[i].group_size > 1 || history[i].price > 0)) {
257+
if (history[i].group_size > 1) {
258+
PrintAndLogEx(SUCCESS, " Group size.......... " _GREEN_("%d"), history[i].group_size);
259+
}
260+
if (history[i].price > 0) {
261+
hrt_price_to_string(history[i].price, price_buf, sizeof(price_buf));
262+
PrintAndLogEx(SUCCESS, " Price............... " _GREEN_("%s"), price_buf);
263+
}
264+
}
265+
266+
hrt_print_time(" Transfer ends.......", history[i].transfer_end_date);
267+
}
268+
}
269+
184270
static void hrt_print_card(const hrt_travel_card_t *card) {
185271
if (card == NULL) return;
186272

@@ -191,6 +277,7 @@ static void hrt_print_card(const hrt_travel_card_t *card) {
191277
const char *product_name = hrt_lookup_product_name(product_code);
192278
int area_type = hrt_travelcard_get_validity_area_type1(card);
193279
int area = hrt_travelcard_get_validity_area1(card);
280+
char boarding_area_buf[64] = {0};
194281
const hrt_eticket_t *value_ticket = hrt_travelcard_get_value_ticket(card);
195282

196283
hrt_price_to_string(
@@ -229,6 +316,15 @@ static void hrt_print_card(const hrt_travel_card_t *card) {
229316
hrt_print_time(" Start date.............", hrt_travelcard_get_period_start_date1(card));
230317
hrt_print_time(" End date...............", hrt_travelcard_get_period_end_date1(card));
231318
PrintAndLogEx(SUCCESS, " Length................. " _GREEN_("%d days"), hrt_travelcard_get_period_length1(card));
319+
hrt_print_time(" Boarding time..........", hrt_travelcard_get_boarding_date(card));
320+
hrt_print_boarding_location(hrt_travelcard_get_boarding_location_num_type(card),
321+
hrt_travelcard_get_boarding_location_num(card));
322+
323+
if (hrt_format_area(hrt_travelcard_get_boarding_area_type(card),
324+
hrt_travelcard_get_boarding_area(card),
325+
boarding_area_buf, sizeof(boarding_area_buf))) {
326+
PrintAndLogEx(SUCCESS, " Boarding zone.......... " _GREEN_("%s"), boarding_area_buf);
327+
}
232328

233329
if (hrt_has_loaded_period_info(card)) {
234330
PrintAndLogEx(SUCCESS, " Organization........... " _GREEN_("%d"), hrt_travelcard_get_period_loading_organization(card));
@@ -240,8 +336,6 @@ static void hrt_print_card(const hrt_travel_card_t *card) {
240336
char value_area_buf[64] = {0};
241337
int value_product_code = hrt_eticket_get_product_code(value_ticket);
242338
const char *value_product_name = hrt_lookup_product_name(value_product_code);
243-
int value_area_type = hrt_eticket_get_validity_area_type(value_ticket);
244-
int value_area = hrt_eticket_get_validity_area(value_ticket);
245339

246340
hrt_price_to_string(hrt_eticket_get_total_fare(value_ticket), fare_buf, sizeof(fare_buf));
247341

@@ -254,10 +348,10 @@ static void hrt_print_card(const hrt_travel_card_t *card) {
254348
PrintAndLogEx(SUCCESS, " Product code........... " _GREEN_("%d (unknown)"), value_product_code);
255349
}
256350

257-
if (hrt_format_area(value_area_type, value_area, value_area_buf, sizeof(value_area_buf))) {
258-
PrintAndLogEx(SUCCESS, " Area................... " _GREEN_("%s"), value_area_buf);
351+
if (hrt_format_value_ticket_zone(value_ticket, value_area_buf, sizeof(value_area_buf))) {
352+
PrintAndLogEx(SUCCESS, " Zone................... " _GREEN_("%s"), value_area_buf);
259353
} else {
260-
PrintAndLogEx(SUCCESS, " Area................... " _GREEN_("%d (type %d)"), value_area, value_area_type);
354+
PrintAndLogEx(SUCCESS, " Zone................... " _GREEN_("unknown"));
261355
}
262356

263357
PrintAndLogEx(SUCCESS, " Fare................... " _GREEN_("%s"), fare_buf);
@@ -266,7 +360,11 @@ static void hrt_print_card(const hrt_travel_card_t *card) {
266360
hrt_print_time(" Valid until............", hrt_eticket_get_validity_end_date(value_ticket));
267361
hrt_print_time(" Boarding time..........", hrt_eticket_get_boarding_date(value_ticket));
268362
PrintAndLogEx(SUCCESS, " Boarding vehicle....... " _GREEN_("%d"), hrt_eticket_get_boarding_vehicle(value_ticket));
363+
hrt_print_boarding_location(hrt_eticket_get_boarding_location_num_type(value_ticket),
364+
hrt_eticket_get_boarding_location_num(value_ticket));
269365
}
366+
367+
hrt_print_history(card);
270368
}
271369

272370
bool is_valid_hrt_card(DesfireContext_t *dctx, const uint8_t *aidbuf, size_t aidbuflen) {

0 commit comments

Comments
 (0)