use std::{sync::{Mutex, OnceLock}, time::{Duration, Instant}}; use tracing::debug; use crate::utils::{parse_access_code, parse_idm_hex}; #[derive(Debug, Clone)] pub (crate) struct StoredCard { pub access_code: [u8; 10], pub card_idm: Option<[u8; 8]>, pub expiry_time: Instant } impl StoredCard { pub fn is_expired(&self) -> bool { Instant::now() > self.expiry_time } } pub (crate) enum CardPresentResult { Accepted, Rejected(String /* reason */), SlotUnavailable(Instant /* release time */), } static PRESENTER: OnceLock = OnceLock::new(); #[derive(Debug)] pub (crate) struct CardPresenter { current_card: Mutex>, } impl Default for CardPresenter { fn default() -> Self { Self { current_card: Mutex::new(None), } } } impl CardPresenter { pub fn instance() -> &'static CardPresenter { PRESENTER.get_or_init(|| CardPresenter::default()) } pub fn request_card(&self, require_idm: Option) -> Option { let mut guard = self.current_card.lock().unwrap(); let card = guard.as_ref()?; if let Some(require) = require_idm && card.card_idm.is_some() != require { return None; } let card = guard.take(); if let Some(ref stored_card) = card && stored_card.is_expired() { debug!("Stored card has expired and will not be provided"); return None; }; card } pub fn present_card(&self, access_code: &String, idm_hex: Option<&String>, validity: Duration) -> CardPresentResult { let mut guard = self.current_card.lock().unwrap(); if let Some(card) = guard.as_ref() && !card.is_expired() { return CardPresentResult::SlotUnavailable(card.expiry_time); } let access_code_bytes = match parse_access_code(access_code) { Some(bytes) => bytes, None => return CardPresentResult::Rejected("Invalid access code format".to_string()), }; let mut card_idm_bytes = None; if let Some(idm_hex) = idm_hex { match parse_idm_hex(idm_hex) { Some(bytes) => card_idm_bytes = Some(bytes), None => return CardPresentResult::Rejected("Invalid IDm format".to_string()), } } let stored_card = StoredCard { access_code: access_code_bytes, card_idm: card_idm_bytes, expiry_time: Instant::now() + validity, }; *guard = Some(stored_card); CardPresentResult::Accepted } }