Krys4lide/crates/fsv/src/ssv/mod.rs

318 lines
12 KiB
Rust

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_13>),
V1_40_14(SSVLibrary<V1_40_14>),
}
/// Struct to hold the SSV library and access its functions
pub struct SSV {
library: SsvLibraryVersion,
}
impl SSV {
const CPS_READER_NAME: &'static str = "Gemalto PC Twin Reader (645D94C3) 00 00";
const VITALE_READER_NAME: &'static str = "Gemalto PC Twin Reader (645D94C3) 00 00"; // TODO: Change this to the correct reader name
pub fn new(version: SupportedFsvVersion) -> Result<Self, Error> {
let library = match version {
SupportedFsvVersion::V1_40_13 => {
let lib_path = get_library_path(&version);
let library = SSVLibrary::<V1_40_13>::new(&lib_path)?;
SsvLibraryVersion::V1_40_13(library)
},
SupportedFsvVersion::V1_40_14 => {
let lib_path = get_library_path(&version);
let library = SSVLibrary::<V1_40_14>::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<Data, Error> {
let pin_code = CString::new(pin_code).expect("CString::new failed");
let cps_pcsc_reader_name = CString::new(SSV::CPS_READER_NAME).expect("CString::new failed");
let vitale_pcsc_reader_name = CString::new(SSV::VITALE_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(
cps_pcsc_reader_name.as_ptr(),
vitale_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(
cps_pcsc_reader_name.as_ptr(),
vitale_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<Data, Error> {
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)
}
pub fn get_vitale_card_rights(&self, pin_code: &str) -> Result<Data, Error> {
let pin_code = CString::new(pin_code).expect("CString::new failed");
let cps_pcsc_reader_name = CString::new(SSV::CPS_READER_NAME).expect("CString::new failed");
let vitale_pcsc_reader_name = CString::new(SSV::VITALE_READER_NAME).expect("CString::new failed");
// Today's date, in the format YYYYMMDD
let today = chrono::Local::now().format("%Y%m%d").to_string();
let date_consultation = CString::new(today).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_droits_vitale(
cps_pcsc_reader_name.as_ptr(),
vitale_pcsc_reader_name.as_ptr(),
pin_code.as_ptr(),
date_consultation.as_ptr(),
&mut out_buffer_ptr,
&mut out_buffer_size)
}?
},
SsvLibraryVersion::V1_40_14(library) => {
unsafe { library.ssv_lire_droits_vitale(
cps_pcsc_reader_name.as_ptr(),
vitale_pcsc_reader_name.as_ptr(),
pin_code.as_ptr(),
date_consultation.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) };
////////////////////////////
println!("{:?}", buffer);
////////////////////////////
let (_rest, vitale_blocks) = Data::from_bytes((buffer, 0)).unwrap();
// Free memory
unsafe { libc::free(out_buffer_ptr) };
Ok(vitale_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<SSV> {
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 if it's not the right 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 if it's not the right 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(())
}
#[test]
#[ignore="
WARNING: Read the card with PIN 1234 - Risk of blocking the card if it's not the right card
WARNING: This test needs a CPS and a VITALE card available simultaneously
"]
fn test_get_vitale_card_rights() -> Result<()> {
let lib = setup::init()?;
let pin_code = "1234";
let _cps_blocks = lib.read_professional_card(pin_code)?;
let _vitale_blocks = lib.get_vitale_card_rights(pin_code)?;
Ok(())
}
}