use std::{ffi::CString, ptr}; use thiserror::Error; use fsv_sys::{ get_library_path, Error as FsvError, SSVLibrary, SSVLibraryCommon, SupportedFsvVersion, V1_40_13, V1_40_14 }; mod errors_ssv; pub use errors_ssv::SSVErrorCodes; use crate::fsv_parsing::prelude::*; #[derive(Error, Debug)] pub enum Error { #[error(transparent)] FSVSysLibrary(#[from] FsvError), #[error(transparent)] SSVError(#[from] SSVErrorCodes), } /// Enum to hold the different versions of the SSV library pub enum SsvLibraryVersion { V1_40_13(SSVLibrary), V1_40_14(SSVLibrary), } /// Struct to hold the SSV library and access its functions pub struct SSV { library: SsvLibraryVersion, } impl SSV { pub fn new(version: SupportedFsvVersion) -> Result { let library = match version { SupportedFsvVersion::V1_40_13 => { let lib_path = get_library_path(&version); let library = SSVLibrary::::new(&lib_path)?; SsvLibraryVersion::V1_40_13(library) }, SupportedFsvVersion::V1_40_14 => { let lib_path = get_library_path(&version); let library = SSVLibrary::::new(&lib_path)?; SsvLibraryVersion::V1_40_14(library) }, }; Ok(Self { library, }) } /// # Initialize the SSV library /// Implement: SSV_InitLIB2 pub fn init_library(&self, sesam_ini_path: &str) -> Result<(), Error> { let sesam_ini_path = CString::new(sesam_ini_path).expect("CString::new failed"); let result = match &self.library { SsvLibraryVersion::V1_40_13(library) => { unsafe { library.ssv_init_lib2(sesam_ini_path.as_ptr()) }? }, SsvLibraryVersion::V1_40_14(library) => { unsafe { library.ssv_init_lib2(sesam_ini_path.as_ptr()) }? }, }; if result != 0 { let error = SSVErrorCodes::from(result); return Err(Error::SSVError(error)); } Ok(()) } /// # Read the CPS card /// Implement: SSV_LireCartePS pub fn read_professional_card(&self, pin_code: &str) -> Result { let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00"; let pin_code = CString::new(pin_code).expect("CString::new failed"); let pcsc_reader_name = CString::new(pcsc_reader_name).expect("CString::new failed"); let mut out_buffer_ptr: *mut libc::c_void = ptr::null_mut(); let mut out_buffer_size: libc::size_t = 0; let result = match &self.library { SsvLibraryVersion::V1_40_13(library) => { unsafe { library.ssv_lire_carte_ps( pcsc_reader_name.as_ptr(), pcsc_reader_name.as_ptr(), pin_code.as_ptr(), &mut out_buffer_ptr, &mut out_buffer_size) }? }, SsvLibraryVersion::V1_40_14(library) => { unsafe { library.ssv_lire_carte_ps( pcsc_reader_name.as_ptr(), pcsc_reader_name.as_ptr(), pin_code.as_ptr(), &mut out_buffer_ptr, &mut out_buffer_size) }? }, }; if result != 0 { // Free memory unsafe { libc::free(out_buffer_ptr) }; let error = SSVErrorCodes::from(result); return Err(Error::SSVError(error)); } // Parse the buffer into a Data struct let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, out_buffer_size) }; let (_rest, cps_blocks) = Data::from_bytes((buffer, 0)).unwrap(); // Free memory unsafe { libc::free(out_buffer_ptr) }; Ok(cps_blocks) } /// # Get the configuration of the SSV library /// Implement: SSV_LireConfig pub fn get_config(&self) -> Result { let mut out_buffer_ptr: *mut libc::c_void = ptr::null_mut(); let mut out_buffer_size: libc::size_t = 0; let result = match &self.library { SsvLibraryVersion::V1_40_13(library) => { unsafe { library.ssv_lire_config(&mut out_buffer_ptr, &mut out_buffer_size) }? }, SsvLibraryVersion::V1_40_14(library) => { unsafe { library.ssv_lire_config(&mut out_buffer_ptr, &mut out_buffer_size) }? }, }; if result != 0 { // Free memory unsafe { libc::free(out_buffer_ptr) }; let error = SSVErrorCodes::from(result); return Err(Error::SSVError(error)); } // Parse the buffer into a Data struct let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, out_buffer_size) }; let (_rest, config_blocks) = Data::from_bytes((buffer, 0)).unwrap(); // Free memory unsafe { libc::free(out_buffer_ptr) }; Ok(config_blocks) } } #[cfg(test)] mod tests { use std::env; use utils::config::load_config; use anyhow::{bail, Result}; use crate::fsv_parsing::{blocks::DataGroup, groups::ssv_lire_carte_ps::{group_1_holder::CardPSType, group_2_situation::{PracticeMode, SpecialtyCode}}}; use super::*; mod setup { use super::*; pub fn init() -> Result { load_config(None)?; let sesam_ini_path = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); let lib = SSV::new(SupportedFsvVersion::V1_40_13)?; lib.init_library(&sesam_ini_path)?; Ok(lib) } } #[test] #[ignore="Not working with other tests using SSV library in parallel - Need to fix"] // We should implement a way to initialize the library only once // Or implement them sequentially with [serial_test crate](https://docs.rs/serial_test/latest/serial_test) fn test_init_library() -> Result<()> { setup::init()?; Ok(()) } #[test] #[ignore=" WARNING: Read the card with PIN 1234 - Risk of blocking the card WARNING: This test will only work with GILBERT's PHARMOFFICE card (titulaire kit pharmacie) "] fn test_read_professional_card_good_pin() -> Result<()> { let lib = setup::init()?; let pin_code = "1234"; let cps_blocks = lib.read_professional_card(pin_code)?; // Check the first group is the holder group let holder_group = cps_blocks.blocks.first().unwrap(); assert_eq!(holder_group.header.group_id.0, 1); let holder_content = match &holder_group.content { DataGroup::LireCartePS_Group1_Holder(content) => { content }, _ => bail!("Wrong group type"), }; assert_eq!(holder_content.card_type, CardPSType::CPS, "Card type"); assert_eq!(holder_content.holder_firstname.0, "GILBERT", "Holder firstname"); // Check the second group is a situation group let situation_group = cps_blocks.blocks.get(1).unwrap(); assert_eq!(situation_group.header.group_id.0, 2); let situation_content = match &situation_group.content { DataGroup::LireCartePS_Group2_Situation(content) => { content }, _ => bail!("Wrong group type"), }; assert_eq!(situation_content.practice_mode, PracticeMode::Liberal, "Practice mode"); assert_eq!(situation_content.specialty_code, SpecialtyCode::PharmacieDOfficine, "Specialty code"); Ok(()) } #[test] #[ignore="WARNING: Read the card with PIN 0000 - Risk of blocking the card"] fn test_read_professional_card_bad_pin() -> Result<()> { let lib = setup::init()?; let pin_code = "0000"; // Should return an error let err = lib.read_professional_card(pin_code).unwrap_err(); assert_eq!(err.to_string(), "Le code porteur présenté est erroné."); match err { Error::SSVError(err) => { assert_eq!(err as SSVErrorCodes, SSVErrorCodes::CPSPinWrong); }, _ => bail!("Error type is not SSVError"), } Ok(()) } #[test] // #[ignore="Needs a valid FSV installation"] fn test_get_config() -> Result<()> { let lib = setup::init()?; let data = lib.get_config()?; // I don't know what to assert here ... let header_group = data.blocks.first().unwrap(); assert_eq!(header_group.header.group_id.0, 60); let header_content = match &header_group.content { DataGroup::LireConfig_Group60_ConfigHeader(content) => { content }, _ => bail!("Wrong group type"), }; assert_eq!(header_content.ssv_version.0.version, "07"); assert_eq!(header_content.ssv_version.0.revision, "20"); Ok(()) } }