Skip to content

Commit

Permalink
First stab at an HSL card reader core written in Rust
Browse files Browse the repository at this point in the history
  • Loading branch information
pingzing committed Jun 29, 2019
0 parents commit 5de6cae
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "scannit-core"
version = "0.1.0"
authors = ["Neil McAlister <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = "0.4"
chrono-tz = "0.5"
lazy_static = "1.3.0"
86 changes: 86 additions & 0 deletions src/desfire.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
mod desfire {
// --- Commands ---
///DESFire GetVersion command.
const GET_VERSION_COMMAND: [u8; 5] = [144, 96, 00, 00, 00];

///DESFire command to return all installed application IDs on the card.
const GET_APPLICATION_IDS_COMMAND: [u8; 5] = [144, 106, 00, 00, 00];

///DESFire Select Application command for selecting the HSL application on the card.
///Returns OkResponse on success.
const SELECT_HSL_COMMAND: [u8; 9] = [144, 90, 00, 00, 03, 20, 32, 239, 00];

///Command to read app info file, which contains application version, card name, etc.
const READ_APP_INFO_COMMAND: [u8; 13] = [144, 189, 00, 00, 07, 08, 00, 00, 00, 11, 00, 00, 00];

///Command to read the season pass file on the card.
const READ_PERIOD_PASS_COMMAND: [u8; 13] =
[144, 189, 00, 00, 07, 01, 00, 00, 00, 32, 00, 00, 00];

///Command to read the stored value on the card.
const READ_STORED_VALUE_COMMAND: [u8; 13] =
[144, 189, 00, 00, 07, 02, 00, 00, 00, 12, 00, 00, 00];

///Command to read the active eTicket on the card.
const READ_E_TICKET_COMMAND: [u8; 13] = [144, 189, 00, 00, 07, 03, 00, 00, 00, 26, 00, 00, 00];

///Command to read the 8 most recent transactions on the card.
const READ_HISTORY_COMMAND: [u8; 13] = [144, 189, 00, 00, 07, 04, 00, 00, 00, 00, 00, 00, 00];

///Reads the remaining bytes-to-be-sent if a read request returned a MoreData response.
const READ_NEXT_COMMAND: [u8; 5] = [144, 175, 00, 00, 00];

// --- Responses ---
///DESFire OPERATION_OK response. In Hex: 0x91, 0x00.
const OK_RESPONSE: [u8; 2] = [145, 00];

///DESFire error response. Not sure what it's known as internally. In Hex: 0x91, 0x9D.
const ERROR_RESPONSE: [u8; 2] = [145, 157];

///DESFire ADDTIONAL_FRAME response. Indicates that more data is expected to be sent. In Hex: 0x91, 0xAF.
const MORE_DATA_RESPONSE: [u8; 2] = [145, 175];

enum Command {
GetVersion,
GetApplicationIds,
SelectHsl,
ReadAppInfo,
ReadPeriodPass,
ReadStoredValue,
ReadETicket,
ReadHistory,
ReadNext,
}

enum Response {
Ok,
Error,
MoreData,
}

impl Command {
fn value(&self) -> &[u8] {
match *self {
Command::GetVersion => &GET_VERSION_COMMAND,
Command::GetApplicationIds => &GET_APPLICATION_IDS_COMMAND,
Command::SelectHsl => &SELECT_HSL_COMMAND,
Command::ReadAppInfo => &READ_APP_INFO_COMMAND,
Command::ReadPeriodPass => &READ_PERIOD_PASS_COMMAND,
Command::ReadStoredValue => &READ_STORED_VALUE_COMMAND,
Command::ReadETicket => &READ_E_TICKET_COMMAND,
Command::ReadHistory => &READ_HISTORY_COMMAND,
Command::ReadNext => &READ_NEXT_COMMAND,
}
}
}

impl Response {
fn value(&self) -> &[u8] {
match *self {
Response::Ok => &OK_RESPONSE,
Response::Error => &ERROR_RESPONSE,
Response::MoreData => &MORE_DATA_RESPONSE,
}
}
}
}
35 changes: 35 additions & 0 deletions src/en1545date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
mod en1545date {
use chrono::prelude::*;
use chrono::Duration;
use chrono_tz::Europe::Helsinki;
use lazy_static::*;

lazy_static! {
static ref EN1545_ZERO_DATE: NaiveDateTime = NaiveDateTime::new(
NaiveDate::from_ymd(1997, 01, 01),
NaiveTime::from_hms(0, 0, 0)
);
}

/// Convert from En1545 (number of days since 1997-01-01) to a standard UTC DateTime.
/// # Arguments
/// * `date` - The date in En1545 format (number of days since 1997-01-01).
fn from_en1545_date(date: u16) -> DateTime<Utc> {
from_en1545_date_and_time(date, 0u16)
}

/// Convert from En1545 (number of days since 1997-01-01, and number of minute since 00:00) to a standard UTC DateTime.
/// # Arguments
/// * `date` - The date in En1545 format (number of days since 1997-01-01).
/// * `time` - The time in En1545 format (number minutes since 00:00).
fn from_en1545_date_and_time(date: u16, time: u16) -> DateTime<Utc> {
let local_datetime =
*EN1545_ZERO_DATE + Duration::days(date as i64) + Duration::minutes(time as i64);
// Assuming Helsinki because it's impossible to use an HSL travel card outside of Finland.
// ...I hope.
Helsinki
.from_local_datetime(&local_datetime)
.unwrap()
.with_timezone(&Utc)
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod desfire;
mod en1545date;
mod travelcard;
7 changes: 7 additions & 0 deletions src/tests/dummy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
15 changes: 15 additions & 0 deletions src/travelcard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
mod travelcard {
pub struct TravelCard {
// todo: lots of fields here once HSL publishes them
}

pub fn create_travel_card(
app_info: &[u8],
period_pass: &[u8],
storedValue: &[u8],
eTicket: &[u8],
history: &[u8],
) -> TravelCard {
TravelCard {}
}
}

0 comments on commit 5de6cae

Please sign in to comment.