additional fields

This commit is contained in:
eta 2022-12-31 14:35:01 +00:00
parent 11b41c3281
commit 1bb6e5cff8
2 changed files with 51 additions and 9 deletions

View File

@ -91,7 +91,7 @@ fn main() -> anyhow::Result<()> {
} else { } else {
eprintln!("[+] checksum ok"); eprintln!("[+] checksum ok");
} }
let ticket = Rsp6Ticket::decode(unpadded)?; let ticket = Rsp6Ticket::decode(unpadded, issuer_id.into())?;
serde_json::to_writer_pretty(std::io::stdout(), &ticket)?; serde_json::to_writer_pretty(std::io::stdout(), &ticket)?;
return Ok(()); return Ok(());
} else { } else {

View File

@ -17,6 +17,13 @@ pub enum CouponType {
ReturnInbound = 3, ReturnInbound = 3,
} }
#[derive(Copy, Clone, Debug, Serialize)]
pub enum DepartTimeFlag {
Mystery = 1,
Specific = 2,
Suggested = 3,
}
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct TicketPurchaseDetails { pub struct TicketPurchaseDetails {
pub purchase_time: PrimitiveDateTime, pub purchase_time: PrimitiveDateTime,
@ -32,6 +39,7 @@ pub struct Reservation {
pub seat_number: u8, pub seat_number: u8,
pub seat_letter: Option<char>, pub seat_letter: Option<char>,
} }
impl Reservation { impl Reservation {
pub fn decode(resv: &BitSlice<u8, Msb0>) -> anyhow::Result<Self> { pub fn decode(resv: &BitSlice<u8, Msb0>) -> anyhow::Result<Self> {
if resv.len() != 45 { if resv.len() != 45 {
@ -57,6 +65,7 @@ impl Reservation {
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct Rsp6Ticket { pub struct Rsp6Ticket {
pub manually_inspect: bool, pub manually_inspect: bool,
pub issuer_id: String,
pub ticket_reference: String, pub ticket_reference: String,
pub checksum: char, pub checksum: char,
pub version: u8, pub version: u8,
@ -71,7 +80,7 @@ pub struct Rsp6Ticket {
pub discount_code: u16, pub discount_code: u16,
pub route_code: u32, pub route_code: u32,
pub start_date: Date, pub start_date: Date,
pub depart_time_flag: u8, pub depart_time_flag: Option<DepartTimeFlag>,
pub depart_time: Time, pub depart_time: Time,
pub passenger_id: Option<String>, pub passenger_id: Option<String>,
pub passenger_name: Option<String>, pub passenger_name: Option<String>,
@ -82,6 +91,9 @@ pub struct Rsp6Ticket {
pub purchase_details: Option<TicketPurchaseDetails>, pub purchase_details: Option<TicketPurchaseDetails>,
pub reservations: Vec<Reservation>, pub reservations: Vec<Reservation>,
pub free_text: Option<String>, pub free_text: Option<String>,
pub osi_nlc: Option<String>,
pub mystery_flag: bool,
pub mystery_header: String,
} }
impl Rsp6Ticket { impl Rsp6Ticket {
@ -132,10 +144,12 @@ impl Rsp6Ticket {
Some(format!("{}{:04}", prefix, (id % 10000))) Some(format!("{}{:04}", prefix, (id % 10000)))
} }
pub fn decode(tkt: &[u8]) -> anyhow::Result<Self> { pub fn decode(tkt: &[u8], issuer_id: String) -> anyhow::Result<Self> {
let bit_tkt = tkt.view_bits::<Msb0>(); let bit_tkt = tkt.view_bits::<Msb0>();
let manually_inspect = bit_tkt[0]; let manually_inspect = bit_tkt[0];
let mystery_header: u8 = bit_tkt[1..8].load_be();
let mystery_header = format!("{:07b}", mystery_header);
let ticket_reference = Self::base64(tkt, 8, 62); let ticket_reference = Self::base64(tkt, 8, 62);
let checksum = Self::base64(tkt, 62, 68).chars().next().unwrap(); let checksum = Self::base64(tkt, 62, 68).chars().next().unwrap();
let version: u8 = bit_tkt[68..72].load_be(); let version: u8 = bit_tkt[68..72].load_be();
@ -162,18 +176,29 @@ impl Rsp6Ticket {
let start_time_secs: u32 = bit_tkt[225..236].load_be(); let start_time_secs: u32 = bit_tkt[225..236].load_be();
let start_time: PrimitiveDateTime = let start_time: PrimitiveDateTime =
CapitalismDateTime::new(start_time_days, start_time_secs).into(); CapitalismDateTime::new(start_time_days, start_time_secs).into();
let depart_time_flag: u8 = bit_tkt[236..238].load_be(); let depart_time_flag = match bit_tkt[236..238].load_be::<u8>() {
0 => None,
1 => Some(DepartTimeFlag::Mystery),
2 => Some(DepartTimeFlag::Specific),
3 => Some(DepartTimeFlag::Suggested),
_ => unreachable!(), // only 2-bit int
};
let depart_time = start_time.time(); let depart_time = start_time.time();
let start_date = start_time.date(); let start_date = start_time.date();
let passenger_id = Self::decode_passenger_id(bit_tkt[238..255].load_be()); let passenger_id = Self::decode_passenger_id(bit_tkt[238..255].load_be());
let passenger_name = Self::base64(tkt, 255, 327); let passenger_name = Self::base64(tkt, 255, 327);
let passenger_name = (!passenger_name.trim().is_empty()).then_some(passenger_name); let passenger_name =
(!passenger_name.trim().is_empty()).then(|| passenger_name.trim().to_owned());
let passenger_gender: u8 = bit_tkt[327..329].load_be(); let passenger_gender: u8 = bit_tkt[327..329].load_be();
let passenger_gender = (passenger_gender != 0).then_some(passenger_gender); let passenger_gender = (passenger_gender != 0).then_some(passenger_gender);
let restriction_code = Self::base64(tkt, 329, 347); let restriction_code = Self::base64(tkt, 329, 347);
let restriction_code = (!restriction_code.trim().is_empty()).then_some(restriction_code); let osi_nlc = Self::base64(tkt, 347, 371);
let osi_nlc = (!osi_nlc.trim().is_empty()).then(|| osi_nlc.trim().to_owned());
let mystery_flag = bit_tkt[371];
let restriction_code =
(!restriction_code.trim().is_empty()).then(|| restriction_code.trim().to_owned());
let bidirectional = bit_tkt[372]; let bidirectional = bit_tkt[372];
let limited_duration = Self::decode_limited_duration(bit_tkt[379..383].load_be()); let limited_duration = Self::decode_limited_duration(bit_tkt[379..383].load_be());
@ -186,8 +211,8 @@ impl Rsp6Ticket {
CapitalismDateTime::new(purchase_time_days, purchase_time_secs).into(); CapitalismDateTime::new(purchase_time_days, purchase_time_secs).into();
let price_pence: u32 = bit_tkt[415..436].load_be(); let price_pence: u32 = bit_tkt[415..436].load_be();
let purchase_reference = Self::base64(tkt, 449, 497); let purchase_reference = Self::base64(tkt, 449, 497);
let purchase_reference = let purchase_reference = (!purchase_reference.trim().is_empty())
(!purchase_reference.trim().is_empty()).then_some(purchase_reference); .then(|| purchase_reference.trim().to_owned());
let mut days_of_validity = bit_tkt[497..506].load_be(); let mut days_of_validity = bit_tkt[497..506].load_be();
if days_of_validity == 0 { if days_of_validity == 0 {
days_of_validity = 1; days_of_validity = 1;
@ -212,8 +237,22 @@ impl Rsp6Ticket {
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let has_free_text = bit_tkt[385];
let free_text_is_extended = bit_tkt[383];
let mut free_text = None;
if has_free_text {
let reservations_end = reservations_start + (45 * reservations_count as usize);
let end = if free_text_is_extended { 863 } else { 783 };
let length = 6 * ((end - reservations_end) / 6);
let text = Self::base64(tkt, reservations_end, reservations_end + length);
if !text.trim().is_empty() {
free_text = Some(text.trim().to_owned());
}
}
Ok(Self { Ok(Self {
manually_inspect, manually_inspect,
issuer_id,
ticket_reference, ticket_reference,
checksum, checksum,
version, version,
@ -238,7 +277,10 @@ impl Rsp6Ticket {
limited_duration, limited_duration,
purchase_details, purchase_details,
reservations, reservations,
free_text: None, free_text,
osi_nlc,
mystery_flag,
mystery_header,
}) })
} }
} }