diff --git a/Cargo.lock b/Cargo.lock index a06acdc..3f85311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1969,6 +1969,7 @@ version = "0.1.0" dependencies = [ "anyhow", "fsv-sys", + "libc", "num_enum", "thiserror", "utils", diff --git a/crates/fsv/Cargo.toml b/crates/fsv/Cargo.toml index 32f6d54..e7bcb4b 100644 --- a/crates/fsv/Cargo.toml +++ b/crates/fsv/Cargo.toml @@ -5,7 +5,8 @@ edition = "2021" [dependencies] anyhow = "1.0.89" -num_enum = "0.7.3" +libc = "0.2.159" +num_enum = { version = "0.7.3", features = ["complex-expressions"] } thiserror = "1.0.64" fsv-sys = { path = "../fsv-sys" } diff --git a/crates/fsv/src/ssv/errors_ssv.rs b/crates/fsv/src/ssv/errors_ssv.rs index 0acf486..350dcd2 100644 --- a/crates/fsv/src/ssv/errors_ssv.rs +++ b/crates/fsv/src/ssv/errors_ssv.rs @@ -4,6 +4,75 @@ use thiserror::Error; #[derive(Error, Debug, Eq, PartialEq, FromPrimitive)] #[repr(u16)] pub enum SSVErrorCodes { + #[error("La Carte du Professionnel de Santé est absente du lecteur.")] + CPSMissing = 0xF001, + #[error("La Carte du Professionnel de Santé bloquée après trois codes porteur erronés.")] + CPSBlocked = 0xF002, + #[error("Le code porteur présenté est erroné.")] + CPSPinWrong = 0xF003, + #[error("Carte du Professionnel de Santé non valide ou inexploitable par le Logiciel Lecteur. Vérifier la présence d'un Domaine d'Assurance Maladie (DAM).")] + CPSInvalid = 0xF004, + #[error("La Carte du Professionnel de Santé est retirée du lecteur.")] + CPSRemoved = 0xF005, + #[error("Message du lecteur incohérent. Débrancher et rebrancher le lecteur.")] + PCSCInconsistentMessage = 0xF0FF, + #[error("Le nom de lecteur fourni ne correspond à aucun lecteur reconnu.")] + PCSCReaderNotFound = 0xF101, + #[error("La fonction InitLIB2 n'est pas encore appelée ou la fonction TermLIB a déjà été appelée.")] + FunctionInitLib2NotCalled = 0xF600, + #[error("La bibliothèque SSV n’est pas chargée en mémoire. Vérifier que la fonction InitLIB2 a bien été appelée.")] + LibraryNotLoaded = 0xF690, // Warning + #[error("Carte vitale en opposition.")] + VitaleOpposition = 0xF6A1, + #[error("Zone de mémoire non allouée en sortie.")] + MemoryNotAllocated = 0xF800, + #[error("Erreur d'allocation de la zone de mémoire en sortie.")] + MemoryAllocationError = 0xF801, + #[error("Un des paramètres obligatoires d'entrée est non alloué ou invalide.")] + InputParameterNotAllocatedOrInvalid = 0xF802, + #[error("Zone de mémoire spécifiée en entrée non valide. Vérifier que la zone allouée ne dépasse pas la taille maximale autorisée (MAXBLOC).")] + InputMemoryInvalid = 0xF803, + #[error("Le format de la zone de mémoire d'entrée ou le nombre de zones mémoire est incorrect.")] + InputMemoryFormatIncorrect = 0xF810, + #[error("Problème lors de l’initialisation du protocole. Erreur du Ressource Manager PC/SC. Vérifiez le lecteur.")] + PCSCProtocolInitError = 0xFF01, + #[error("Time-out au niveau protocolaire ou transmission déjà en cours avec le lecteur. Vérifiez le lecteur et l'insertion de la carte.")] + PCSCProtocolTimeout = 0xFF02, + #[error("Taille insuffisante allouée en entrée d’une fonction du Resource Manager.")] + PCSCProtocolInputMemoryTooSmall = 0xFF03, + #[error("Erreur de transmission du protocole. Vérifiez le lecteur et l'insertion de la carte.")] + PCSCProtocolTransmissionError = 0xFF04, + #[error("Lecteur absent ou indisponible.")] + PCSCReaderMissingOrUnavailable = 0xFF05, + #[error("Le nom du lecteur transmis est inconnu du Resource Manager PC/SC.")] + PCSCReaderUnknown = 0xFF06, + #[error("Erreur inconnue remontée par le Resource Manager PC/SC.")] + PCSCUnknownError = 0xFF07, + #[error("Erreur interne Resource Manager PC/SC.")] + PCSCInternalError = 0xFF08, + #[error("Ressource PC/SC déjà prise en exclusivité. Vérifiez qu'une autre application n'utilise pas le lecteur.")] + PCSCResourceAlreadyExclusive = 0xFF09, + #[error("Protocole incompatible avec la carte à puce. Vérifiez l'insertion de la carte et son état.")] + PCSCProtocolIncompatible = 0xFF0A, + #[error("Paramètre incorrect. Erreur interne à la librairie SSV.")] + PCSCIncorrectParameter = 0xFF0B, + #[error("Carte absente. Insérez une carte dans le lecteur.")] + PCSCCardMissing = 0xFF0C, + #[error("L'état de la carte a été modifié (RAZ ou mise hors tension). Vérifiez si la carte n'a pas été retirée ou si une autre application n'utilise pas la carte.")] + PCSCCardStateChanged = 0xFF0D, + #[error("Carte muette ou non supportée. Vérifiez l'insertion de la carte.")] + PCSCCardUnsupported = 0xFF0E, + #[error("Code porteur CPS non renseigné.")] + CPSPinMissing = 0xFF21, + #[error("Ressource PC/SC déjà prise en exclusivité. Vérifiez que le processus en cours n'utilise pas déjà le lecteur.")] + PCSCReaderAlreadyExclusiveForCurrentProcess = 0xFF24, + #[error("Plusieurs lecteurs ou cartes de même type identifiés lors de la détection automatique.")] + PCSCDuplicatedReadersOrCardsDetected = 0xFF29, + #[error("Problème de chargement de la librairie cryptographique ou erreur retournée par la librairie cryptographique.")] + CryptoLibraryError = 0xFF30, + #[error("Erreurs internes aux Services SESAM-Vitale. Vérifiez les traces.")] + #[num_enum(alternatives = [0xFFF1..=0xFFFF])] + SSVInternalError = 0xFFF0, #[error("Le fichier `tablebin.smc` est inaccessible en lecture (inexistant ou pas de droits d'accès).")] FileMissingTablebinMsc = 0xF610, // tablebin.smc #[error("Le fichier `scripts.sms` est inaccessible en lecture (inexistant ou pas de droits d'accès).")] @@ -80,3 +149,27 @@ pub enum SSVErrorCodes { #[error("Erreur inattendue de la librairie SSV (code d'erreur: {0}).")] Unexpected(u16), } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_code_ranges() { + let error_code = 0xFFF1; + let error = SSVErrorCodes::from(error_code); + assert_eq!(error, SSVErrorCodes::SSVInternalError); + + let error_code = 0xFFF8; + let error = SSVErrorCodes::from(error_code); + assert_eq!(error, SSVErrorCodes::SSVInternalError); + } + + #[test] + fn test_catch_all() { + let error_code = 0xFBFF; // Not a valid error code + let error = SSVErrorCodes::from(error_code); + assert_eq!(error, SSVErrorCodes::Unexpected(0xFBFF)); + } +} + \ No newline at end of file diff --git a/crates/fsv/src/ssv/mod.rs b/crates/fsv/src/ssv/mod.rs index 0cd773a..fdd8d55 100644 --- a/crates/fsv/src/ssv/mod.rs +++ b/crates/fsv/src/ssv/mod.rs @@ -1,3 +1,5 @@ +use std::{ffi::CString, ptr}; + use thiserror::Error; use fsv_sys::{ @@ -54,7 +56,7 @@ impl SSV { /// # Initialize the SSV library /// Implement: SSV_InitLIB2 pub fn init_library(&self, sesam_ini_path: &str) -> Result<(), Error> { - let sesam_ini_path = std::ffi::CString::new(sesam_ini_path).expect("CString::new failed"); + 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()) }? @@ -69,6 +71,51 @@ impl SSV { } Ok(()) } + + /// # Read the CPS card + /// Implement: SSV_LireCartePS + pub fn read_professional_card(&self, pin_code: &str) -> Result<(), Error> { + 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)); + } + // Print 10 bytes of the buffer + let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, 10) }; + println!("{:?}", buffer); + // Free memory + unsafe { libc::free(out_buffer_ptr) }; + Ok(()) + } } #[cfg(test)] @@ -80,16 +127,46 @@ mod tests { use super::*; - fn init() -> Result { - load_config().unwrap(); - Ok(SSV::new(SupportedFsvVersion::V1_40_13)?) + mod setup { + use super::*; + + pub fn init() -> Result { + load_config().unwrap(); + 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] fn test_init_library() -> Result<()> { - let lib = init()?; - let sesam_ini_path = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); - lib.init_library(&sesam_ini_path)?; + setup::init()?; + Ok(()) + } + + #[test] + fn test_read_professional_card_good_pin() -> Result<()> { + let lib = setup::init()?; + let pin_code = "1234"; + lib.read_professional_card(pin_code)?; + Ok(()) + } + + #[ignore] + #[test] + 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); + }, + _ => panic!("Error type is not SSVError"), + } Ok(()) }