scannit_core/
history.rs

1use crate::conversion::*;
2use crate::en1545date::from_en1545_date_and_time;
3use chrono::prelude::*;
4
5#[derive(Debug)]
6pub struct History {
7    pub transaction_type: TransactionType,
8    pub boarding_datetime: DateTime<Utc>,
9    pub transfer_end_datetime: DateTime<Utc>,
10    pub ticket_fare_cents: u16,
11    pub group_size: u8,
12    /// Value remaining on the card after this use. Always 0 if this was a season pass usage.
13    pub remaining_value: u32,
14}
15
16pub fn create_history_entries(history_bytes: &[u8]) -> Vec<History> {
17    let num_entries = history_bytes.len() / 12; // Each history entry is 12 bytes.
18    let entry_size = 96; // in bits. 8 * 12.
19    let mut history_entries: Vec<History> = vec![];
20
21    for i in 0..num_entries {
22        // We're far enough away from the En1545 zero-date that one of the first three
23        // bits of the date field should always be 1. This is sufficient to see if
24        // there is any data in for this history entry.
25        if history_bytes[i * 12 + 1] == 0
26            && history_bytes[i * 12 + 2] == 0
27            && history_bytes[i * 12 + 3] == 0
28            && history_bytes[i * 13 + 3] == 0
29        {
30            continue;
31        }
32        let entry_offset = i * entry_size;
33        let transaction_type = get_bits_as_u8(history_bytes, 0 + entry_offset, 1);
34        let boarding_date = get_bits_as_u16(history_bytes, 1 + entry_offset, 14);
35        let boarding_time = get_bits_as_u16(history_bytes, 15 + entry_offset, 11);
36        let end_date = get_bits_as_u16(history_bytes, 26 + entry_offset, 14);
37        let end_time = get_bits_as_u16(history_bytes, 40 + entry_offset, 11);
38        let ticket_fare = get_bits_as_u16(history_bytes, 51 + entry_offset, 14);
39        let group_size = get_bits_as_u8(history_bytes, 65 + entry_offset, 6);
40        let remaining_value = get_bits_as_u32(history_bytes, 71 + entry_offset, 20);
41
42        history_entries.push(History {
43            transaction_type: TransactionType::new(transaction_type),
44            boarding_datetime: from_en1545_date_and_time(boarding_date, boarding_time),
45            transfer_end_datetime: from_en1545_date_and_time(end_date, end_time),
46            ticket_fare_cents: ticket_fare,
47            group_size,
48            remaining_value,
49        });
50    }
51
52    history_entries
53}
54
55#[repr(u32)]
56#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
57pub enum TransactionType {
58    SeasonPass = 0,
59    ValueTicket = 1,
60}
61
62impl TransactionType {
63    pub(crate) fn new(type_value: u8) -> TransactionType {
64        match type_value {
65            0 => TransactionType::SeasonPass,
66            1 => TransactionType::ValueTicket,
67            _ => panic!("Unsupported TransactionType value"),
68        }
69    }
70}