scannit_core/
travelcard.rs

1use crate::conversion::*;
2use crate::en1545date::{from_en1545_date, from_en1545_date_and_time};
3use crate::eticket::*;
4use crate::history::*;
5use crate::models::*;
6use chrono::prelude::*;
7
8#[derive(Debug)]
9pub struct TravelCard {
10    // Application Info
11    pub application_version: u8,
12    pub application_key_version: u8,
13    pub application_instance_id: String,
14    pub platform_type: u8,
15    pub is_mac_protected: bool,
16
17    // Control info
18    pub application_issuing_date: DateTime<Utc>,
19    pub application_status: bool,
20    pub application_unblocking_number: u8,
21    pub application_transaction_counter: u32,
22    pub action_list_counter: u32,
23
24    // Period pass
25    pub period_pass: PeriodPass,
26
27    // Last load info
28    pub stored_value_cents: u32,
29    pub last_load_datetime: DateTime<Utc>,
30    pub last_load_value: u32,
31    pub last_load_organization_id: u16,
32    pub last_load_device_num: u16,
33
34    // E-Ticket
35    pub e_ticket: ETicket,
36
37    // History
38    pub history: Vec<History>,
39}
40
41#[derive(Debug)]
42pub struct PeriodPass {
43    pub product_code_1: ProductCode,
44    pub validity_area_1: ValidityArea,
45    pub period_start_date_1: Date<Utc>,
46    pub period_end_date_1: Date<Utc>,
47
48    // This _seems_ to be the last-known season pass before the switchover to the new card format.
49    // Probably part of the migration path when they were doing the changeover.
50    pub product_code_2: ProductCode,
51    pub validity_area_2: ValidityArea,
52    pub period_start_date_2: Date<Utc>,
53    pub period_end_date_2: Date<Utc>,
54
55    // Most recent card load:
56    pub loaded_period_product: ProductCode,
57    pub loaded_period_datetime: DateTime<Utc>,
58    pub loaded_period_length: u16,
59    pub loaded_period_price: u32, // in cents
60    pub loading_organization: u16,
61    pub loading_device_number: u16,
62
63    // Last use/boarding:
64    pub last_board_datetime: DateTime<Utc>,
65    pub last_board_vehicle_number: u16,
66    pub last_board_location: BoardingLocation,
67    pub last_board_direction: BoardingDirection,
68    pub last_board_area: BoardingArea,
69}
70
71pub fn create_travel_card(
72    app_info: &[u8],
73    control_info: &[u8],
74    period_pass: &[u8],
75    stored_value: &[u8],
76    e_ticket: &[u8],
77    history: &[u8],
78) -> TravelCard {
79    println!(
80        "Lengths: app_info: {:?}, period_pass: {:?}, history: {:?}",
81        app_info.len(),
82        period_pass.len(),
83        history.len()
84    );
85
86    let (app_version, app_key_version, app_instance_id, platform, is_protected) =
87        read_application_info(app_info);
88    let (issue_date, app_status, unblock_number, transaction_counter, action_counter) =
89        read_control_info(control_info);
90    let period_pass = read_period_pass(period_pass);
91    let stored_value = read_stored_value(stored_value);
92    let e_ticket = create_e_ticket(e_ticket);
93    let history = create_history_entries(history);
94
95    TravelCard {
96        application_version: app_version,
97        application_key_version: app_key_version,
98        application_instance_id: app_instance_id,
99        platform_type: platform,
100        is_mac_protected: is_protected,
101
102        application_issuing_date: issue_date,
103        application_status: app_status,
104        application_unblocking_number: unblock_number,
105        application_transaction_counter: transaction_counter,
106        action_list_counter: action_counter,
107
108        period_pass,
109
110        stored_value_cents: stored_value.cents,
111        last_load_datetime: stored_value.last_load_datetime,
112        last_load_value: stored_value.last_load_value,
113        last_load_organization_id: stored_value.last_load_organization_id,
114        last_load_device_num: stored_value.last_load_device_num,
115
116        e_ticket,
117        history,
118    }
119}
120
121// Notes about travel card data: All data is presented as a pile of bytes,
122// and all bytes are expressed in Big Endian format.
123
124fn read_application_info(app_info: &[u8]) -> (u8, u8, String, u8, bool) {
125    (
126        get_bits_as_u8(app_info, 0, 4),       // Application Version
127        get_bits_as_u8(app_info, 4, 4), // Application Key Version (though the spec sheet marks it as "reserved")
128        as_hex_string(&app_info[1..10]), // Application Instance ID (aka the card's unique ID number)
129        get_bits_as_u8(app_info, 80, 3), // Platform Type, 0 = NXP DESFire 4kB.
130        get_bits_as_u8(app_info, 83, 1) != 0, // SecurityLevel, which is a 1-bit field. 0 = open, 1 = MAC protected.
131    )
132}
133
134fn read_control_info(control_info: &[u8]) -> (DateTime<Utc>, bool, u8, u32, u32) {
135    let issuing_date = get_bits_as_u16(control_info, 0, 14);
136    (
137        from_en1545_date(issuing_date),
138        get_bits_as_u8(control_info, 14, 1) != 0, // 1-bit app status (no idea what status *means*, but...)
139        // Skip a single reserved bit here
140        get_bits_as_u8(control_info, 16, 8), // 8-bit 'unblocking number' (ditto, no idea)
141        get_bits_as_u32(control_info, 24, 24), // Application transaction counter, 24-bits long
142        get_bits_as_u32(control_info, 48, 32), // Action List Counter, 32-bits long
143    )
144}
145
146fn read_period_pass(period_pass: &[u8]) -> PeriodPass {
147    let product_code_type_1 = get_bits_as_u8(period_pass, 0, 1);
148    let product_code_1 = get_bits_as_u16(period_pass, 1, 14);
149    let validity_area_type_1 = get_bits_as_u8(period_pass, 15, 2);
150    let validity_area_1 = get_bits_as_u8(period_pass, 17, 6);
151    let start_date_1 = get_bits_as_u16(period_pass, 23, 14);
152    let end_date_1 = get_bits_as_u16(period_pass, 37, 14);
153    let product_code_type_2 = get_bits_as_u8(period_pass, 56, 1);
154    let product_code_2 = get_bits_as_u16(period_pass, 57, 14);
155    let validity_area_type_2 = get_bits_as_u8(period_pass, 71, 2);
156    let validity_area_2 = get_bits_as_u8(period_pass, 73, 6);
157    let start_date_2 = get_bits_as_u16(period_pass, 79, 14);
158    let end_date_2 = get_bits_as_u16(period_pass, 93, 14);
159
160    let loaded_period_product_type = get_bits_as_u8(period_pass, 112, 1);
161    let loaded_period_product = get_bits_as_u16(period_pass, 113, 14);
162    let loaded_period_date = get_bits_as_u16(period_pass, 127, 14);
163    let loaded_period_time = get_bits_as_u16(period_pass, 141, 11);
164    let loaded_period_length = get_bits_as_u16(period_pass, 152, 9);
165    let loaded_period_price = get_bits_as_u32(period_pass, 161, 20);
166    let loading_organization = get_bits_as_u16(period_pass, 181, 14);
167    let loading_device_number = get_bits_as_u16(period_pass, 195, 13);
168
169    let last_board_date = get_bits_as_u16(period_pass, 208, 14);
170    let last_board_time = get_bits_as_u16(period_pass, 222, 11);
171    let last_board_vehicle_number = get_bits_as_u16(period_pass, 233, 14);
172    let last_board_location_num_type = get_bits_as_u8(period_pass, 247, 2);
173    let last_board_location_num = get_bits_as_u16(period_pass, 249, 14);
174    let last_board_direction = get_bits_as_u8(period_pass, 263, 1);
175    let last_board_area_type = get_bits_as_u8(period_pass, 264, 2);
176    let last_board_area = get_bits_as_u8(period_pass, 266, 6);
177    PeriodPass {
178        product_code_1: ProductCode::new(product_code_type_1, product_code_1),
179        validity_area_1: ValidityArea::new(validity_area_type_1, validity_area_1),
180        period_start_date_1: from_en1545_date(start_date_1).date(),
181        period_end_date_1: from_en1545_date(end_date_1).date(),
182
183        product_code_2: ProductCode::new(product_code_type_2, product_code_2),
184        validity_area_2: ValidityArea::new(validity_area_type_2, validity_area_2),
185        period_start_date_2: from_en1545_date(start_date_2).date(),
186        period_end_date_2: from_en1545_date(end_date_2).date(),
187
188        loaded_period_product: ProductCode::new(loaded_period_product_type, loaded_period_product),
189        loaded_period_datetime: from_en1545_date_and_time(loaded_period_date, loaded_period_time),
190        loaded_period_length,
191        loaded_period_price,
192        loading_organization,
193        loading_device_number,
194
195        last_board_datetime: from_en1545_date_and_time(last_board_date, last_board_time),
196        last_board_vehicle_number,
197        last_board_location: BoardingLocation::new(
198            last_board_location_num_type,
199            last_board_location_num,
200        ),
201        last_board_direction: BoardingDirection::from(last_board_direction),
202        last_board_area: BoardingArea::new(last_board_area_type, last_board_area),
203    }
204}
205
206fn read_stored_value(stored_value: &[u8]) -> StoredValue {
207    let last_load_date = get_bits_as_u16(stored_value, 20, 14);
208    let last_load_time = get_bits_as_u16(stored_value, 34, 11);
209
210    StoredValue {
211        cents: get_bits_as_u32(stored_value, 0, 20),
212        last_load_datetime: from_en1545_date_and_time(last_load_date, last_load_time),
213        last_load_value: get_bits_as_u32(stored_value, 45, 20),
214        last_load_organization_id: get_bits_as_u16(stored_value, 65, 14),
215        last_load_device_num: get_bits_as_u16(stored_value, 79, 14),
216    }
217}
218
219struct StoredValue {
220    cents: u32,
221    last_load_datetime: DateTime<Utc>,
222    last_load_value: u32,
223    last_load_organization_id: u16,
224    last_load_device_num: u16,
225}