From f799f471bcfc742374a9f9709c339bd16a5ebda2 Mon Sep 17 00:00:00 2001 From: lienjukaisim Date: Mon, 29 Jul 2024 20:36:14 +0200 Subject: [PATCH 01/11] feat: Implement a first version of the decode_zone_memoire function --- crates/sesam-vitale/src/ssvlib_demo.rs | 107 ++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index a8f4731..98cb961 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -1,24 +1,19 @@ +extern crate dotenv; /** * High level API for the SSV library, * based on the low level bindings in libssv.rs. - * + * */ - extern crate libc; -extern crate dotenv; -use libc::{ c_void, size_t }; +use libc::{c_void, size_t}; use std::ffi::CString; use std::path::PathBuf; use std::ptr; use std::env; -use crate::libssv:: { - SSV_InitLIB2, - SSV_LireCartePS, - SSV_LireConfig -}; +use crate::libssv::{SSV_InitLIB2, SSV_LireCartePS, SSV_LireConfig}; fn ssv_init_lib_2() { let ini_str = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); @@ -36,18 +31,19 @@ fn ssv_lire_carte_ps() { let mut buffer: *mut c_void = ptr::null_mut(); let mut size: size_t = 0; + let mut hex_values: &[u8] = &[]; unsafe { let result = SSV_LireCartePS( resource_ps.as_ptr(), resource_reader.as_ptr(), card_number.as_ptr(), &mut buffer, - &mut size + &mut size, ); println!("SSV_LireCartePS result: {}", result); if !buffer.is_null() { - let hex_values = std::slice::from_raw_parts(buffer as *const u8, size); + hex_values = std::slice::from_raw_parts(buffer as *const u8, size); for &byte in hex_values { print!("{:02X} ", byte); } @@ -56,6 +52,93 @@ fn ssv_lire_carte_ps() { libc::free(buffer); } } + decode_zone_memoire(hex_values); +} + +pub fn decode_zone_memoire(bytes: &[u8]) { + // Maintenant, vous pouvez accéder aux octets individuels dans `donnees` + for &octet in bytes { + println!("Octet: {}", octet); + } + + let mut current_pos_general = 0; + let mut current_groupe = 0; + let mut num_champ = 0; + + while current_pos_general < bytes.len() - 1 { + num_champ = 0; + current_groupe = + 256 * bytes[current_pos_general] as i32 + bytes[current_pos_general + 1] as i32; + current_pos_general += 2; + + let longueur_groupe: usize; + if bytes[current_pos_general] < 128 { + longueur_groupe = bytes[current_pos_general] as usize; + } else { + let nbre_octets_longueur = bytes[current_pos_general] - 128; + longueur_groupe = (0..nbre_octets_longueur).fold(0, |acc, i| { + current_pos_general += 1; + acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize + }); + } + current_pos_general += 1; + + let mut current_pos_groupe = 0; + while current_pos_groupe < longueur_groupe { + num_champ += 1; + let longueur_champs: usize; + let nbre_octets_longueur: usize; + if bytes[current_pos_general] < 128 { + longueur_champs = bytes[current_pos_general] as usize; + nbre_octets_longueur = 1; + } else { + nbre_octets_longueur = bytes[current_pos_general] as usize - 128; + longueur_champs = (0..nbre_octets_longueur).fold(0, |acc, i| { + current_pos_general += 1; + current_pos_groupe += 1; + acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize + }); + } + + let mut bytes_champ = vec![0; longueur_champs]; + if longueur_champs > 0 { + bytes_champ.copy_from_slice( + &bytes[current_pos_general + nbre_octets_longueur + ..current_pos_general + nbre_octets_longueur + longueur_champs], + ); + + match current_groupe { + 1 => match num_champ { + 1 => println!("Groupe1.Champ1: {}", String::from_utf8_lossy(&bytes_champ)), + 2 => println!("Groupe1.Champ2: {}", String::from_utf8_lossy(&bytes_champ)), + 3 => println!("Groupe1.Champ3: {}", String::from_utf8_lossy(&bytes_champ)), + 4 => println!("Groupe1.Champ4: {}", String::from_utf8_lossy(&bytes_champ)), + 5 => println!("Groupe1.Champ5: {}", String::from_utf8_lossy(&bytes_champ)), + 6 => println!("Groupe1.Champ6: {}", String::from_utf8_lossy(&bytes_champ)), + 7 => println!("Groupe1.Champ7: {}", String::from_utf8_lossy(&bytes_champ)), + 8 => println!("Groupe1.Champ8: {}", String::from_utf8_lossy(&bytes_champ)), + _ => (), + }, + 2 => match num_champ { + 1 => { + // Ajoutez votre logique pour Groupe2 ici + } + 2 => { + // Ajoutez votre logique pour Groupe2 ici + } + 3 => { + // Ajoutez votre logique pour Groupe2 ici + } + _ => (), + }, + _ => (), + } + } + + current_pos_general += longueur_champs + nbre_octets_longueur; + current_pos_groupe += longueur_champs + nbre_octets_longueur; + } + } } fn ssv_lire_config() { @@ -81,7 +164,7 @@ pub fn demo() { /* * TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env` * Maybe we could use a system standard config path to store a config file - */ + */ let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let manifest_path = PathBuf::from(manifest_dir); dotenv::from_path(manifest_path.join(".env")).ok(); From d65c8699499200c6bca5b4cd9ef1c8dab5e6e862 Mon Sep 17 00:00:00 2001 From: lienjukaisim <154924955+lienjukaisim@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:43:15 +0200 Subject: [PATCH 02/11] feat: add the structure to reprensent CPS fields returned by the lire_carte_ps function --- crates/sesam-vitale/src/main.rs | 1 + crates/sesam-vitale/src/ssvlib_demo.rs | 268 ++++++++++++++++++++++--- 2 files changed, 245 insertions(+), 24 deletions(-) diff --git a/crates/sesam-vitale/src/main.rs b/crates/sesam-vitale/src/main.rs index b863fbb..77bd4e9 100644 --- a/crates/sesam-vitale/src/main.rs +++ b/crates/sesam-vitale/src/main.rs @@ -3,4 +3,5 @@ mod libssv; fn main() { ssvlib_demo::demo(); + // XXX } diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index 98cb961..21f5a94 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -24,10 +24,88 @@ fn ssv_init_lib_2() { } } +/* +1. CB = Caractères Binaires » +2. CE = Caractères « Etendus » (ISO 8859-1) +3. CA = Caractères Alphanumériques (ASCII?) +4. CN = Caractères Numériques +*/ + +#[derive(Debug)] +struct CartePS { + titulaire: TitulairePS, + situations: Vec, +} +#[derive(Debug)] +struct TitulairePS { + type_de_carte_ps: String, // CN + type_d_identification_nationale: String, // CN + numero_d_identification_nationale: String, // CE - 8 -> 30 + cle_du_numero_d_identification_nationale: String, // CN + code_civilite: String, // CN + nom_du_ps: String, // CE - 27 + prenom_du_ps: String, // CE - 27 + categorie_carte: char, // CA +} + +#[derive(Debug)] +struct SituationPS { + numero_logique_de_la_situation_de_facturation_du_ps: u8, + mode_d_exercice: String, + statut_d_exercice: String, + secteur_d_activite: String, + type_d_identification_structure: String, + numero_d_identification_structure: String, + cle_du_numero_d_identification_structure: String, + raison_sociale_structure: String, + numero_d_identification_de_facturation_du_ps: String, + cle_du_numero_d_identification_de_facturation_du_ps: String, + numero_d_identification_du_ps_remplaçant: String, + cle_du_numero_d_identification_du_ps_remplaçant: String, + code_conventionnel: String, + code_specialite: String, + code_zone_tarifaire: String, + code_zone_ik: String, + code_agrement_1: String, + code_agrement_2: String, + code_agrement_3: String, + habilitation_à_signer_une_facture: String, + habilitation_à_signer_un_lot: String, +} + +impl Default for SituationPS { + fn default() -> Self { + Self { + numero_logique_de_la_situation_de_facturation_du_ps: 0, + mode_d_exercice: String::new(), + statut_d_exercice: String::new(), + secteur_d_activite: String::new(), + type_d_identification_structure: String::new(), + numero_d_identification_structure: String::new(), + cle_du_numero_d_identification_structure: String::new(), + raison_sociale_structure: String::new(), + numero_d_identification_de_facturation_du_ps: String::new(), + cle_du_numero_d_identification_de_facturation_du_ps: String::new(), + numero_d_identification_du_ps_remplaçant: String::new(), + cle_du_numero_d_identification_du_ps_remplaçant: String::new(), + code_conventionnel: String::new(), + code_specialite: String::new(), + code_zone_tarifaire: String::new(), + code_zone_ik: String::new(), + code_agrement_1: String::new(), + code_agrement_2: String::new(), + code_agrement_3: String::new(), + habilitation_à_signer_une_facture: String::new(), + habilitation_à_signer_un_lot: String::new(), + } + } +} + fn ssv_lire_carte_ps() { - let resource_ps = CString::new("PS").expect("CString::new failed"); - let resource_reader = CString::new("TRANSPA1").expect("CString::new failed"); - let card_number = CString::new("1234567890").expect("CString::new failed"); + let resource_ps = + CString::new("HID Global OMNIKEY 3x21 Smart Card Reader 0").expect("CString::new failed"); + let resource_reader = CString::new("").expect("CString::new failed"); + let card_number = CString::new("1234").expect("CString::new failed"); let mut buffer: *mut c_void = ptr::null_mut(); let mut size: size_t = 0; @@ -44,11 +122,6 @@ fn ssv_lire_carte_ps() { if !buffer.is_null() { hex_values = std::slice::from_raw_parts(buffer as *const u8, size); - for &byte in hex_values { - print!("{:02X} ", byte); - } - println!(); - libc::free(buffer); } } @@ -57,14 +130,24 @@ fn ssv_lire_carte_ps() { pub fn decode_zone_memoire(bytes: &[u8]) { // Maintenant, vous pouvez accéder aux octets individuels dans `donnees` - for &octet in bytes { - println!("Octet: {}", octet); - } - let mut current_pos_general = 0; let mut current_groupe = 0; let mut num_champ = 0; + let mut carte_ps = CartePS { + titulaire: TitulairePS { + type_de_carte_ps: String::new(), + type_d_identification_nationale: String::new(), + numero_d_identification_nationale: String::new(), + cle_du_numero_d_identification_nationale: String::new(), + code_civilite: String::new(), + nom_du_ps: String::new(), + prenom_du_ps: String::new(), + categorie_carte: ' ', + }, + situations: Vec::new(), + }; + while current_pos_general < bytes.len() - 1 { num_champ = 0; current_groupe = @@ -109,25 +192,160 @@ pub fn decode_zone_memoire(bytes: &[u8]) { match current_groupe { 1 => match num_champ { - 1 => println!("Groupe1.Champ1: {}", String::from_utf8_lossy(&bytes_champ)), - 2 => println!("Groupe1.Champ2: {}", String::from_utf8_lossy(&bytes_champ)), - 3 => println!("Groupe1.Champ3: {}", String::from_utf8_lossy(&bytes_champ)), - 4 => println!("Groupe1.Champ4: {}", String::from_utf8_lossy(&bytes_champ)), - 5 => println!("Groupe1.Champ5: {}", String::from_utf8_lossy(&bytes_champ)), - 6 => println!("Groupe1.Champ6: {}", String::from_utf8_lossy(&bytes_champ)), - 7 => println!("Groupe1.Champ7: {}", String::from_utf8_lossy(&bytes_champ)), - 8 => println!("Groupe1.Champ8: {}", String::from_utf8_lossy(&bytes_champ)), + 1 => { + carte_ps.titulaire.type_de_carte_ps = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 2 => { + carte_ps.titulaire.type_d_identification_nationale = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 3 => { + carte_ps.titulaire.numero_d_identification_nationale = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 4 => { + carte_ps.titulaire.cle_du_numero_d_identification_nationale = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 5 => { + carte_ps.titulaire.code_civilite = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 6 => { + carte_ps.titulaire.nom_du_ps = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 7 => { + carte_ps.titulaire.prenom_du_ps = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 8 => { + carte_ps.titulaire.categorie_carte = bytes_champ[0] as char; + } _ => (), }, 2 => match num_champ { 1 => { - // Ajoutez votre logique pour Groupe2 ici + carte_ps.situations.push(SituationPS::default()); + carte_ps + .situations + .last_mut() + .unwrap() + .numero_logique_de_la_situation_de_facturation_du_ps = + bytes_champ[0]; } 2 => { - // Ajoutez votre logique pour Groupe2 ici + carte_ps.situations.last_mut().unwrap().mode_d_exercice = + String::from_utf8_lossy(&bytes_champ).to_string(); } 3 => { - // Ajoutez votre logique pour Groupe2 ici + carte_ps.situations.last_mut().unwrap().statut_d_exercice = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 4 => { + carte_ps.situations.last_mut().unwrap().secteur_d_activite = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 5 => { + carte_ps + .situations + .last_mut() + .unwrap() + .type_d_identification_structure = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 6 => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_structure = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 7 => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_structure = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 8 => { + carte_ps + .situations + .last_mut() + .unwrap() + .raison_sociale_structure = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 9 => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_de_facturation_du_ps = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 10 => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_de_facturation_du_ps = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 11 => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_du_ps_remplaçant = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 12 => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_du_ps_remplaçant = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 13 => { + carte_ps.situations.last_mut().unwrap().code_conventionnel = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 14 => { + carte_ps.situations.last_mut().unwrap().code_specialite = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 15 => { + carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 16 => { + carte_ps.situations.last_mut().unwrap().code_zone_ik = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 17 => { + carte_ps.situations.last_mut().unwrap().code_agrement_1 = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 18 => { + carte_ps.situations.last_mut().unwrap().code_agrement_2 = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 19 => { + carte_ps.situations.last_mut().unwrap().code_agrement_3 = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 20 => { + carte_ps.situations.last_mut().unwrap().habilitation_à_signer_une_facture = + String::from_utf8_lossy(&bytes_champ).to_string(); + } + 21 => { + carte_ps.situations.last_mut().unwrap().habilitation_à_signer_un_lot = + String::from_utf8_lossy(&bytes_champ).to_string(); } _ => (), }, @@ -139,6 +357,8 @@ pub fn decode_zone_memoire(bytes: &[u8]) { current_pos_groupe += longueur_champs + nbre_octets_longueur; } } + + println!("CartePS: {:#?}", carte_ps); } fn ssv_lire_config() { @@ -173,7 +393,7 @@ pub fn demo() { ssv_init_lib_2(); ssv_lire_carte_ps(); - ssv_lire_config(); + // ssv_lire_config(); println!("-----------------------------------------"); } From 2e07f0b7d105bb7052406bf851be428a703bb3b9 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Tue, 30 Jul 2024 15:15:24 +0200 Subject: [PATCH 03/11] feat: add a function to read a "bloc / field size" in SSV memory --- crates/sesam-vitale/src/main.rs | 1 + crates/sesam-vitale/src/ssv_memory.rs | 119 ++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 crates/sesam-vitale/src/ssv_memory.rs diff --git a/crates/sesam-vitale/src/main.rs b/crates/sesam-vitale/src/main.rs index 77bd4e9..dfe2455 100644 --- a/crates/sesam-vitale/src/main.rs +++ b/crates/sesam-vitale/src/main.rs @@ -1,5 +1,6 @@ mod ssvlib_demo; mod libssv; +mod ssv_memory; fn main() { ssvlib_demo::demo(); diff --git a/crates/sesam-vitale/src/ssv_memory.rs b/crates/sesam-vitale/src/ssv_memory.rs new file mode 100644 index 0000000..1031ec2 --- /dev/null +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -0,0 +1,119 @@ +/** + * Provide functions to manipulate raw memory from SSV library. + */ + +// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ? + +#[derive(PartialEq, Debug)] +pub struct ElementSize { + pub size: usize, + pub pad: usize, +} + +#[derive(PartialEq, Debug)] +pub enum MemoryParsingError<'a> { + MemoryIsEmpty(&'a str), + MemoryIsNotValid(&'a str), +} + +pub fn read_element_size(bytes: &[u8]) -> Result { + /* Longueur: + * - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet + * - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur + */ + if bytes.len() == 0 { + return Err(MemoryParsingError::MemoryIsEmpty("Empty bytes input")); + } + + let mut element_size = ElementSize { size: 0, pad: 1 }; + if bytes[0] & 0b1000_0000 == 0 { + // Size coded on 1 byte + element_size.size = bytes[0] as usize; + } else { + // Size coded on N bytes + // N are the 7 lower bits of the first byte + let size_bytes_len = (bytes[0] & 0b0111_1111) as usize; + if size_bytes_len > bytes.len() - 1 { + return Err(MemoryParsingError::MemoryIsNotValid( + "Invalid memory: not enough bytes to read the size", + )); + } else if size_bytes_len > 4 { + return Err(MemoryParsingError::MemoryIsNotValid( + "Invalid memory: size is too big", + )); + } + let size_bytes = &bytes[1..1 + size_bytes_len]; + + // u32::from_be_bytes() requires a 4 bytes array + let mut padded_bytes = [0u8; 4]; + padded_bytes[size_bytes_len..].copy_from_slice(&size_bytes); + + element_size.size = u32::from_be_bytes(padded_bytes) as usize; + element_size.pad += size_bytes_len; + } + Ok(element_size) +} + +#[cfg(test)] +mod test_read_element_size { + use super::*; + + #[test] + fn short_size() { + let bytes = [0b_0000_0001_u8]; + let element_size = read_element_size(&bytes).unwrap(); + assert_eq!(element_size.size, 1); + assert_eq!(element_size.pad, 1); + + let bytes = [0b_0100_0000_u8]; + let element_size = read_element_size(&bytes).unwrap(); + assert_eq!(element_size.size, 64); + assert_eq!(element_size.pad, 1); + } + + #[test] + fn long_size() { + let bytes = [0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0100_0000_u8]; + let element_size = read_element_size(&bytes).unwrap(); + assert_eq!(element_size.size, 320); + assert_eq!(element_size.pad, 3); + } + + #[test] + fn null_size() { + let bytes = []; + let toto = "toto"; + // Expect an error + assert_eq!( + read_element_size(&bytes), + Err(MemoryParsingError::MemoryIsEmpty("Empty bytes input")), + ); + } + + #[test] + fn invalid_memory() { + let bytes = [0b_1000_0001_u8]; + assert_eq!( + read_element_size(&bytes), + Err(MemoryParsingError::MemoryIsNotValid( + "Invalid memory: not enough bytes to read the size" + )), + ); + + let bytes = [0b_1000_0010_u8, 1]; + assert_eq!( + read_element_size(&bytes), + Err(MemoryParsingError::MemoryIsNotValid( + "Invalid memory: not enough bytes to read the size" + )), + ); + + let bytes = [0b_1000_0101_u8, 1, 1, 1, 1, 1]; + assert_eq!( + read_element_size(&bytes), + Err(MemoryParsingError::MemoryIsNotValid( + "Invalid memory: size is too big" + )), + ); + } +} From 4def46745d66e1e7af7bf52264bc6e2d56f699fc Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Tue, 30 Jul 2024 15:53:19 +0200 Subject: [PATCH 04/11] feat: add read_element function for raw bloc or field parsing --- crates/sesam-vitale/src/ssv_memory.rs | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/sesam-vitale/src/ssv_memory.rs b/crates/sesam-vitale/src/ssv_memory.rs index 1031ec2..c20acd6 100644 --- a/crates/sesam-vitale/src/ssv_memory.rs +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -54,6 +54,20 @@ pub fn read_element_size(bytes: &[u8]) -> Result, +} + +pub fn read_element(bytes: &[u8]) -> Element { + let ElementSize { size, pad } = read_element_size(bytes).unwrap(); + let contenu = bytes[pad..pad+size].to_vec(); + Element { + pad: pad+size, + contenu, + } +} + #[cfg(test)] mod test_read_element_size { use super::*; @@ -117,3 +131,35 @@ mod test_read_element_size { ); } } + +#[cfg(test)] +mod test_read_element { + use super::*; + + #[test] + fn short_size() { + let bytes = [51, + 1, 48, 1, 56, 11, 57, 57, 55, 48, 48, + 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, + 50, 17, 80, 72, 65, 82, 77, 65, 67, 73, + 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, + 70, 82, 65, 78, 67, 79, 73, 83, 69, 1, + 84, + ]; + let element = read_element(&bytes); + assert_eq!(element.pad, 52); + assert_eq!(element.contenu[..5], [1, 48, 1, 56, 11]); + } + + #[test] + fn long_size() { + let mut bytes = vec![0b_1000_0010_u8, + 0b_0000_0001_u8, 0b_0000_0000_u8, // size = 256 + ]; + // Add 256 bytes to the content + bytes.append(&mut vec![1; 256]); + let element = read_element(&bytes); + assert_eq!(element.pad, 259); + assert_eq!(element.contenu.len(), 256); + } +} \ No newline at end of file From 13c2f7573b88d593c775782d99928f5e8ae08d18 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Tue, 30 Jul 2024 21:27:10 +0200 Subject: [PATCH 05/11] feat: implement block and fields as Struct implementing From trait --- crates/sesam-vitale/src/ssv_memory.rs | 327 +++++++++++++++++++------- 1 file changed, 243 insertions(+), 84 deletions(-) diff --git a/crates/sesam-vitale/src/ssv_memory.rs b/crates/sesam-vitale/src/ssv_memory.rs index c20acd6..d00369a 100644 --- a/crates/sesam-vitale/src/ssv_memory.rs +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -4,141 +4,178 @@ // TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ? +use std::convert::TryFrom; + #[derive(PartialEq, Debug)] -pub struct ElementSize { +struct ElementSize { pub size: usize, pub pad: usize, } -#[derive(PartialEq, Debug)] -pub enum MemoryParsingError<'a> { - MemoryIsEmpty(&'a str), - MemoryIsNotValid(&'a str), -} +impl TryFrom<&[u8]> for ElementSize { + type Error = &'static str; -pub fn read_element_size(bytes: &[u8]) -> Result { - /* Longueur: - * - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet - * - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur - */ - if bytes.len() == 0 { - return Err(MemoryParsingError::MemoryIsEmpty("Empty bytes input")); - } - - let mut element_size = ElementSize { size: 0, pad: 1 }; - if bytes[0] & 0b1000_0000 == 0 { - // Size coded on 1 byte - element_size.size = bytes[0] as usize; - } else { - // Size coded on N bytes - // N are the 7 lower bits of the first byte - let size_bytes_len = (bytes[0] & 0b0111_1111) as usize; - if size_bytes_len > bytes.len() - 1 { - return Err(MemoryParsingError::MemoryIsNotValid( - "Invalid memory: not enough bytes to read the size", - )); - } else if size_bytes_len > 4 { - return Err(MemoryParsingError::MemoryIsNotValid( - "Invalid memory: size is too big", - )); + fn try_from(bytes: &[u8]) -> Result { + /* Longueur: + * - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet + * - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur + */ + if bytes.len() == 0 { + return Err("Empty bytes input"); } - let size_bytes = &bytes[1..1 + size_bytes_len]; - // u32::from_be_bytes() requires a 4 bytes array - let mut padded_bytes = [0u8; 4]; - padded_bytes[size_bytes_len..].copy_from_slice(&size_bytes); + let mut element_size = ElementSize { size: 0, pad: 1 }; + if bytes[0] & 0b1000_0000 == 0 { + // Size coded on 1 byte + element_size.size = bytes[0] as usize; + } else { + // Size coded on N bytes + // N are the 7 lower bits of the first byte + let size_bytes_len = (bytes[0] & 0b0111_1111) as usize; + if size_bytes_len > bytes.len() - 1 { + return Err("Invalid memory: not enough bytes to read the size"); + } else if size_bytes_len > 4 { + return Err("Invalid memory: size is too big"); + } + let size_bytes = &bytes[1..1 + size_bytes_len]; - element_size.size = u32::from_be_bytes(padded_bytes) as usize; - element_size.pad += size_bytes_len; + // u32::from_be_bytes() requires a 4 bytes array + let mut padded_bytes = [0u8; 4]; + padded_bytes[size_bytes_len..].copy_from_slice(&size_bytes); + + element_size.size = u32::from_be_bytes(padded_bytes) as usize; + element_size.pad += size_bytes_len; + } + Ok(element_size) } - Ok(element_size) } -pub struct Element { - pub pad: usize, - pub contenu: Vec, +#[derive(Debug)] +pub struct Block<'a> { + pub id: u16, + pub size: usize, + pub content: Vec>, } -pub fn read_element(bytes: &[u8]) -> Element { - let ElementSize { size, pad } = read_element_size(bytes).unwrap(); - let contenu = bytes[pad..pad+size].to_vec(); - Element { - pad: pad+size, - contenu, +impl<'a> From<&'a [u8]> for Block<'a> { + fn from(bytes: &'a [u8]) -> Self { + let mut offset = 0; + let id = u16::from_be_bytes(bytes[..2].try_into().unwrap()); + offset += 2; + let ElementSize { size: block_size, pad } = bytes[2..].try_into().unwrap(); + offset += pad; + let raw_content = &bytes[offset..]; + let mut field_offset = 0; + // While there is still content to read, parse Fields + let mut content = Vec::new(); + while field_offset < block_size { + let field: Field<'a> = raw_content[field_offset..].into(); + field_offset += field.size; + content.push(field); + } + Block { + id, + size: offset + block_size, + content, + } } } +#[derive(Debug)] +pub struct Field<'a> { + pub size: usize, + pub content: &'a [u8], +} + +impl<'a> From<&'a [u8]> for Field<'a> { + fn from(bytes: &'a [u8]) -> Self { + let ElementSize { size, pad } = bytes.try_into().unwrap(); + let contenu = &bytes[pad..pad+size]; + Field { + size: pad+size, + content: contenu, + } + } +} + +pub fn decode_ssv_memory(bytes: &[u8], size: usize) -> Vec { + let mut blocks: Vec = Vec::new(); + let mut offset = 0; + while offset < size { + let block: Block = bytes[offset..].into(); + offset += block.size; + blocks.push(block); + } + blocks +} + #[cfg(test)] -mod test_read_element_size { +mod test_element_size { use super::*; #[test] fn short_size() { - let bytes = [0b_0000_0001_u8]; - let element_size = read_element_size(&bytes).unwrap(); + let bytes: &[u8] = &[0b_0000_0001_u8]; + let element_size: ElementSize = bytes.try_into().unwrap(); assert_eq!(element_size.size, 1); assert_eq!(element_size.pad, 1); - let bytes = [0b_0100_0000_u8]; - let element_size = read_element_size(&bytes).unwrap(); + let bytes: &[u8] = &[0b_0100_0000_u8]; + let element_size: ElementSize = bytes.try_into().unwrap(); assert_eq!(element_size.size, 64); assert_eq!(element_size.pad, 1); } #[test] fn long_size() { - let bytes = [0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0100_0000_u8]; - let element_size = read_element_size(&bytes).unwrap(); + let bytes: &[u8] = &[0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0100_0000_u8]; + let element_size: ElementSize = bytes.try_into().unwrap(); assert_eq!(element_size.size, 320); assert_eq!(element_size.pad, 3); } #[test] fn null_size() { - let bytes = []; - let toto = "toto"; - // Expect an error + let bytes: &[u8] = &[]; + let result: Result = bytes.try_into(); assert_eq!( - read_element_size(&bytes), - Err(MemoryParsingError::MemoryIsEmpty("Empty bytes input")), + result, + Err("Empty bytes input"), ); } #[test] fn invalid_memory() { - let bytes = [0b_1000_0001_u8]; + let bytes: &[u8] = &[0b_1000_0001_u8]; + let result: Result = bytes.try_into(); assert_eq!( - read_element_size(&bytes), - Err(MemoryParsingError::MemoryIsNotValid( - "Invalid memory: not enough bytes to read the size" - )), + result, + Err("Invalid memory: not enough bytes to read the size"), ); - let bytes = [0b_1000_0010_u8, 1]; + let bytes: &[u8] = &[0b_1000_0010_u8, 1]; + let result: Result = bytes.try_into(); assert_eq!( - read_element_size(&bytes), - Err(MemoryParsingError::MemoryIsNotValid( - "Invalid memory: not enough bytes to read the size" - )), + result, + Err("Invalid memory: not enough bytes to read the size"), ); - let bytes = [0b_1000_0101_u8, 1, 1, 1, 1, 1]; + let bytes: &[u8] = &[0b_1000_0101_u8, 1, 1, 1, 1, 1]; + let result: Result = bytes.try_into(); assert_eq!( - read_element_size(&bytes), - Err(MemoryParsingError::MemoryIsNotValid( - "Invalid memory: size is too big" - )), + result, + Err("Invalid memory: size is too big"), ); } } #[cfg(test)] -mod test_read_element { +mod test_field { use super::*; #[test] fn short_size() { - let bytes = [51, + let bytes: &[u8] = &[51, 1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, 50, 17, 80, 72, 65, 82, 77, 65, 67, 73, @@ -146,20 +183,142 @@ mod test_read_element { 70, 82, 65, 78, 67, 79, 73, 83, 69, 1, 84, ]; - let element = read_element(&bytes); - assert_eq!(element.pad, 52); - assert_eq!(element.contenu[..5], [1, 48, 1, 56, 11]); + let element: Field = bytes.try_into().unwrap(); + assert_eq!(element.size, 52); + assert_eq!(element.content[..5], [1, 48, 1, 56, 11]); } #[test] fn long_size() { - let mut bytes = vec![0b_1000_0010_u8, + let mut bytes_vec = vec![0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0000_0000_u8, // size = 256 ]; // Add 256 bytes to the content - bytes.append(&mut vec![1; 256]); - let element = read_element(&bytes); - assert_eq!(element.pad, 259); - assert_eq!(element.contenu.len(), 256); + bytes_vec.append(&mut vec![1; 256]); + let bytes: &[u8] = &bytes_vec; + let element: Field = bytes.try_into().unwrap(); + assert_eq!(element.size, 259); + assert_eq!(element.content.len(), 256); + } +} + +#[cfg(test)] +mod test_block { + use super::*; + + #[test] + fn test_francoise_pharmacien0052419_partial_block_1() { + let bytes: &[u8] = &[ + 1, 48, + 1, 56, + 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, + ]; + + let field1: Field = bytes.into(); + assert_eq!(field1.size, 2); + assert_eq!(field1.content, &[48]); + + let field2: Field = bytes[field1.size..].into(); + assert_eq!(field2.size, 2); + assert_eq!(field2.content, &[56]); + + let field3: Field = bytes[field1.size + field2.size..].into(); + assert_eq!(field3.size, 12); + assert_eq!(field3.content, &[57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52]); + } + + #[test] + fn test_francoise_pharmacien0052419() { + let bytes: &[u8] = &[ + 0, 1, 51, // 3 + 1, 48, // 2 + 1, 56, // 2 + 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12 + 1, 52, // 2 + 2, 50, 50, // 3 + 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18 + 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10 + 1, 84, // 2 + // total: 54 + + 0, 2, 83, + 1, 1, + 1, 48, + 1, 49, + 2, 56, 54, + 1, 49, + 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, + 1, 56, + 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, 57, 53, + 8, 48, 48, 50, 48, 50, 52, 49, 57, + 1, 56, + 0, + 1, 48, + 1, 49, + 2, 53, 48, + 2, 49, 48, + 2, 48, 48, + 1, 48, + 1, 48, + 1, 48, + 1, 49, + 1, 49, + ]; + + let first_block: Block = bytes.into(); + assert_eq!(first_block.id, 1); + assert_eq!(first_block.size, 54); + assert_eq!(first_block.content.len(), 8); + + let second_block: Block = bytes[first_block.size..].into(); + assert_eq!(second_block.id, 2); + assert_eq!(second_block.size, 86); + assert_eq!(second_block.content.len(), 21); + } +} + +#[cfg(test)] +mod test_decode_ssv_memory { + use super::*; + + #[test] + fn test_francoise_pharmacien0052419() { + let bytes: &[u8] = &[ + 0, 1, 51, // 3 + 1, 48, // 2 + 1, 56, // 2 + 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12 + 1, 52, // 2 + 2, 50, 50, // 3 + 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18 + 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10 + 1, 84, // 2 + // total: 54 + + 0, 2, 83, + 1, 1, + 1, 48, + 1, 49, + 2, 56, 54, + 1, 49, + 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, + 1, 56, + 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, 57, 53, + 8, 48, 48, 50, 48, 50, 52, 49, 57, + 1, 56, + 0, + 1, 48, + 1, 49, + 2, 53, 48, + 2, 49, 48, + 2, 48, 48, + 1, 48, + 1, 48, + 1, 48, + 1, 49, + 1, 49, + ]; + let blocks = decode_ssv_memory(&bytes, bytes.len()); + assert_eq!(blocks.len(), 2); } } \ No newline at end of file From 0be0b08f8907e713f1c96ee273375109f57428bf Mon Sep 17 00:00:00 2001 From: lienjukaisim <154924955+lienjukaisim@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:46:58 +0200 Subject: [PATCH 06/11] feat: implement some more structured version of the memory decoding and the mapping to CPS fields --- crates/sesam-vitale/src/ssvlib_demo.rs | 223 ++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 5 deletions(-) diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index 21f5a94..16b6e42 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -101,11 +101,13 @@ impl Default for SituationPS { } } -fn ssv_lire_carte_ps() { + + +fn ssv_lire_carte_ps(code_pin :&str, lecteur :&str) { let resource_ps = - CString::new("HID Global OMNIKEY 3x21 Smart Card Reader 0").expect("CString::new failed"); + CString::new(lecteur).expect("CString::new failed"); let resource_reader = CString::new("").expect("CString::new failed"); - let card_number = CString::new("1234").expect("CString::new failed"); + let card_number = CString::new(code_pin).expect("CString::new failed"); let mut buffer: *mut c_void = ptr::null_mut(); let mut size: size_t = 0; @@ -126,8 +128,40 @@ fn ssv_lire_carte_ps() { } } decode_zone_memoire(hex_values); + + /* IDÉE : implémenter decode_zone_memoire sous forme d'itérateur + for (group, group_rank) in decode_zone_memoire(...) { + for (field, field_rank) in group { + match group_rank { + GROUP_1: { + match field_rank { + 1 => carte_ps.titulaire.type_de_carte_ps = field.into(), + 2 => carte_ps.titulaire.type_d_identification_nationale = field.into(), + 3 => carte_ps.titulaire.numero_d_identification_nationale = field.into(), + 4 => carte_ps.titulaire.cle_du_numero_d_identification_nationale = field.into(), + 5 => carte_ps.titulaire.code_civilite = field.into(), + 6 => carte_ps.titulaire.nom_du_ps = field.into(), + 7 => carte_ps.titulaire.prenom_du_ps = field.into(), + 8 => carte_ps.titulaire.categorie_carte = field.into(), + _ => (), + } + } + GROUP_2: { + match field_rank { + ... + } + } + } + } + } + */ } +/** + * Options possibles : + * - Passer un objet de type variable (CartePS, CarteVitale...) et le remplir intelligemment + * - Retourner un itérateur et faire le remplissage en dehors de decode_zone_memoire + */ pub fn decode_zone_memoire(bytes: &[u8]) { // Maintenant, vous pouvez accéder aux octets individuels dans `donnees` let mut current_pos_general = 0; @@ -389,11 +423,190 @@ pub fn demo() { let manifest_path = PathBuf::from(manifest_dir); dotenv::from_path(manifest_path.join(".env")).ok(); - println!("------- Demo for the SSV library --------"); + println!("------- Demo for the SSV library 2 --------"); ssv_init_lib_2(); - ssv_lire_carte_ps(); + let code_pin = "1234"; + let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0"; + let carte_ps = ssv_lire_carte_ps2(code_pin, lecteur); + println!("CartePS: {:#?}", carte_ps); // ssv_lire_config(); println!("-----------------------------------------"); } + + +struct field{ + rank : i32, + value : Vec +} +impl Default for field { + fn default() -> Self { + Self {rank: 0, + value: Vec::new(), + } + } +} +pub struct group{ + rank : i32, + fields : Vec +} +impl Default for group { + fn default() -> Self { + Self {rank: 0, + fields: Vec::new(), + } + } +} + + pub fn decode_zone_memoire2(bytes: &[u8]) -> Vec{ + + let mut current_pos_general = 0; + let mut current_groupe = 0; + let mut num_champ = 0; + let mut longueur_groupe: usize; + let mut current_pos_groupe = 0; + let mut groups : Vec = Vec::new(); + + while current_pos_general < (bytes.len() - 1) { + num_champ = 0; + current_groupe = + 256 * bytes[current_pos_general] as i32 + bytes[current_pos_general + 1 ] as i32; + current_pos_general += 2; + + groups.push(group::default()); + groups.last_mut().unwrap().rank=current_groupe; + + if bytes[current_pos_general] < 128 { + longueur_groupe = bytes[current_pos_general] as usize; + } else { + let nbre_octets_longueur = bytes[current_pos_general] - 128; + longueur_groupe = (0..nbre_octets_longueur).fold(0, |acc, i| { + current_pos_general += 1; + acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize + }); + } + current_pos_general += 1; + println!("------- 3--------"); + current_pos_groupe = 0; + while current_pos_groupe < longueur_groupe { + num_champ += 1; + let longueur_champs: usize; + let nbre_octets_longueur: usize; + if bytes[current_pos_general] < 128 { + longueur_champs = bytes[current_pos_general] as usize; + nbre_octets_longueur = 1; + } else { + nbre_octets_longueur = bytes[current_pos_general] as usize - 128; + longueur_champs = (0..nbre_octets_longueur).fold(0, |acc, i| { + current_pos_general += 1; + current_pos_groupe += 1; + acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize + }); + } + + groups.last_mut().unwrap().fields.push(field::default()); + groups.last_mut().unwrap().fields.last_mut().unwrap().rank=num_champ; + + if longueur_champs > 0 { + let mut bytes_champ = vec![0; longueur_champs]; + bytes_champ.copy_from_slice( + &bytes[current_pos_general + nbre_octets_longueur + ..current_pos_general + nbre_octets_longueur + longueur_champs], + ); + + //groups.last_mut().unwrap().fields.last_mut().unwrap().value= String::from_utf8_lossy(&bytes_champ).to_string(); + groups.last_mut().unwrap().fields.last_mut().unwrap().value= bytes_champ; + + + } + current_pos_general += longueur_champs + nbre_octets_longueur; + current_pos_groupe += longueur_champs + nbre_octets_longueur; + } + } + groups +} + +fn ssv_lire_carte_ps2(code_pin :&str, lecteur :&str) -> CartePS{ + let resource_ps = CString::new(lecteur).expect("CString::new failed"); + let resource_reader = CString::new("").expect("CString::new failed"); + let card_number = CString::new(code_pin).expect("CString::new failed"); + + let mut buffer: *mut c_void = ptr::null_mut(); + let mut size: size_t = 0; + let mut hex_values: &[u8] = &[]; + unsafe { + let result = SSV_LireCartePS( + resource_ps.as_ptr(), + resource_reader.as_ptr(), + card_number.as_ptr(), + &mut buffer, + &mut size, + ); + println!("SSV_LireCartePS result: {}", result); + + if !buffer.is_null() { + hex_values = std::slice::from_raw_parts(buffer as *const u8, size); + libc::free(buffer); + } + } + let groups= decode_zone_memoire2(hex_values); + let mut carte_ps = CartePS { + titulaire: TitulairePS { + type_de_carte_ps: String::new(), + type_d_identification_nationale: String::new(), + numero_d_identification_nationale: String::new(), + cle_du_numero_d_identification_nationale: String::new(), + code_civilite: String::new(), + nom_du_ps: String::new(), + prenom_du_ps: String::new(), + categorie_carte: ' ', + }, + situations: Vec::new(), +}; +for item_group in groups.iter(){ + for item_field in item_group.fields.iter(){ + match (item_group.rank,item_field.rank) { + (1, 1) => {carte_ps.titulaire.type_de_carte_ps = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 2) => {carte_ps.titulaire.type_d_identification_nationale = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 3) => {carte_ps.titulaire.numero_d_identification_nationale = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 4) => {carte_ps.titulaire.cle_du_numero_d_identification_nationale = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 5) => {carte_ps.titulaire.code_civilite = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 6) => {carte_ps.titulaire.nom_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 7) => {carte_ps.titulaire.prenom_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} + (1, 8) => unsafe {carte_ps.titulaire.categorie_carte = char::from_u32_unchecked(item_field.value[0] as u32)} + (2, 1) => {carte_ps.situations.push(SituationPS::default()); + carte_ps.situations.last_mut().unwrap().numero_logique_de_la_situation_de_facturation_du_ps = item_field.value[0];} + (2, 2) => {carte_ps.situations.last_mut().unwrap().mode_d_exercice = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 3) => {carte_ps.situations.last_mut().unwrap().statut_d_exercice = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 4) => {carte_ps.situations.last_mut().unwrap().secteur_d_activite = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 5) => {carte_ps.situations.last_mut().unwrap().type_d_identification_structure = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 6) => {carte_ps.situations.last_mut().unwrap().numero_d_identification_structure = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 7) => {carte_ps.situations.last_mut().unwrap().cle_du_numero_d_identification_structure = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 8) => {carte_ps.situations.last_mut().unwrap().raison_sociale_structure = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 9) => {carte_ps.situations.last_mut().unwrap().numero_d_identification_de_facturation_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 10) => {carte_ps.situations.last_mut().unwrap().cle_du_numero_d_identification_de_facturation_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 11) => {carte_ps.situations.last_mut().unwrap().numero_d_identification_du_ps_remplaçant = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 12) => {carte_ps.situations.last_mut().unwrap().cle_du_numero_d_identification_du_ps_remplaçant = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 13) => {carte_ps.situations.last_mut().unwrap().code_conventionnel = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 14) => {carte_ps.situations.last_mut().unwrap().code_specialite = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 15) => {carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 16) => {carte_ps.situations.last_mut().unwrap().code_zone_ik = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 17) => {carte_ps.situations.last_mut().unwrap().code_agrement_1 = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 18) => {carte_ps.situations.last_mut().unwrap().code_agrement_2 = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 19) => {carte_ps.situations.last_mut().unwrap().code_agrement_3 = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 20) => {carte_ps.situations.last_mut().unwrap().habilitation_à_signer_une_facture = String::from_utf8_lossy(&item_field.value).to_string();} + (2, 21) => {carte_ps.situations.last_mut().unwrap().habilitation_à_signer_un_lot = String::from_utf8_lossy(&item_field.value).to_string();} + _ => (), + } + } +} +carte_ps + +} + + + + + + From e9ef6cbb4b0fb1e592d8d318189e7c428660c1d5 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Fri, 2 Aug 2024 00:08:49 +0200 Subject: [PATCH 07/11] feat: cleaning of all the ssv_memory and ssvlib_demo to get a cleaner cps::lire_carte API --- crates/sesam-vitale/src/cps.rs | 347 +++++++++++++++ crates/sesam-vitale/src/lib.rs | 5 + crates/sesam-vitale/src/libssv.rs | 18 +- crates/sesam-vitale/src/main.rs | 3 +- crates/sesam-vitale/src/ssv_memory.rs | 143 +++---- crates/sesam-vitale/src/ssvlib_demo.rs | 562 +------------------------ 6 files changed, 425 insertions(+), 653 deletions(-) create mode 100644 crates/sesam-vitale/src/cps.rs diff --git a/crates/sesam-vitale/src/cps.rs b/crates/sesam-vitale/src/cps.rs new file mode 100644 index 0000000..f332265 --- /dev/null +++ b/crates/sesam-vitale/src/cps.rs @@ -0,0 +1,347 @@ +use libc::{c_void, size_t}; +use std::ffi::CString; +use std::ptr; + +/* +1. CB = Caractères Binaires » +2. CE = Caractères « Etendus » (ISO 8859-1) +3. CA = Caractères Alphanumériques (ASCII?) +4. CN = Caractères Numériques +*/ + +use crate::libssv::SSV_LireCartePS; +use crate::ssv_memory::{decode_ssv_memory, Block}; + +#[derive(Debug, Default)] +pub struct CartePS { + titulaire: TitulairePS, + situations: Vec, +} + +#[derive(Debug, Default)] +struct TitulairePS { + type_de_carte_ps: String, // CN + type_d_identification_nationale: String, // CN + numero_d_identification_nationale: String, // CE - 8 -> 30 + cle_du_numero_d_identification_nationale: String, // CN + code_civilite: String, // CN + nom_du_ps: String, // CE - 27 + prenom_du_ps: String, // CE - 27 + categorie_carte: char, // CA +} + +#[derive(Debug, Default)] +struct SituationPS { + numero_logique_de_la_situation_de_facturation_du_ps: u8, + mode_d_exercice: String, + statut_d_exercice: String, + secteur_d_activite: String, + type_d_identification_structure: String, + numero_d_identification_structure: String, + cle_du_numero_d_identification_structure: String, + raison_sociale_structure: String, + numero_d_identification_de_facturation_du_ps: String, + cle_du_numero_d_identification_de_facturation_du_ps: String, + numero_d_identification_du_ps_remplaçant: String, + cle_du_numero_d_identification_du_ps_remplaçant: String, + code_conventionnel: String, + code_specialite: String, + code_zone_tarifaire: String, + code_zone_ik: String, + code_agrement_1: String, + code_agrement_2: String, + code_agrement_3: String, + habilitation_à_signer_une_facture: String, + habilitation_à_signer_un_lot: String, +} + +pub fn lire_carte(code_pin: &str, lecteur: &str) -> CartePS { + let resource_ps = CString::new(lecteur).expect("CString::new failed"); + let resource_reader = CString::new("").expect("CString::new failed"); + let card_number = CString::new(code_pin).expect("CString::new failed"); + + let mut buffer: *mut c_void = ptr::null_mut(); + let mut size: size_t = 0; + let mut hex_values: &[u8] = &[]; + unsafe { + let result = SSV_LireCartePS( + resource_ps.as_ptr(), + resource_reader.as_ptr(), + card_number.as_ptr(), + &mut buffer, + &mut size, + ); + println!("SSV_LireCartePS result: {}", result); + + if !buffer.is_null() { + hex_values = std::slice::from_raw_parts(buffer as *const u8, size); + libc::free(buffer); + } + } + let groups = decode_ssv_memory(hex_values, hex_values.len()); + decode_carte_ps(groups) +} + +fn decode_carte_ps(groups: Vec) -> CartePS { + let mut carte_ps = CartePS::default(); + for group in groups { + for (field_index, field) in group.content.iter().enumerate() { + match (group.id, field_index + 1) { + (1, 1) => { + carte_ps.titulaire.type_de_carte_ps = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 2) => { + carte_ps.titulaire.type_d_identification_nationale = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 3) => { + carte_ps.titulaire.numero_d_identification_nationale = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 4) => { + carte_ps.titulaire.cle_du_numero_d_identification_nationale = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 5) => { + carte_ps.titulaire.code_civilite = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 6) => { + carte_ps.titulaire.nom_du_ps = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 7) => { + carte_ps.titulaire.prenom_du_ps = + String::from_utf8_lossy(field.content).to_string(); + } + (1, 8) => { + let byte = field.content[0]; + carte_ps.titulaire.categorie_carte = byte as char; + } + (2, 1) => { + carte_ps.situations.push(SituationPS::default()); + carte_ps + .situations + .last_mut() + .unwrap() + .numero_logique_de_la_situation_de_facturation_du_ps = field.content[0]; + } + (2, 2) => { + carte_ps.situations.last_mut().unwrap().mode_d_exercice = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 3) => { + carte_ps.situations.last_mut().unwrap().statut_d_exercice = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 4) => { + carte_ps.situations.last_mut().unwrap().secteur_d_activite = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 5) => { + carte_ps + .situations + .last_mut() + .unwrap() + .type_d_identification_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 6) => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 7) => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 8) => { + carte_ps + .situations + .last_mut() + .unwrap() + .raison_sociale_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 9) => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_de_facturation_du_ps = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 10) => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_de_facturation_du_ps = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 11) => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_du_ps_remplaçant = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 12) => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_du_ps_remplaçant = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 13) => { + carte_ps.situations.last_mut().unwrap().code_conventionnel = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 14) => { + carte_ps.situations.last_mut().unwrap().code_specialite = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 15) => { + carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 16) => { + carte_ps.situations.last_mut().unwrap().code_zone_ik = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 17) => { + carte_ps.situations.last_mut().unwrap().code_agrement_1 = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 18) => { + carte_ps.situations.last_mut().unwrap().code_agrement_2 = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 19) => { + carte_ps.situations.last_mut().unwrap().code_agrement_3 = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 20) => { + carte_ps + .situations + .last_mut() + .unwrap() + .habilitation_à_signer_une_facture = + String::from_utf8_lossy(field.content).to_string(); + } + (2, 21) => { + carte_ps + .situations + .last_mut() + .unwrap() + .habilitation_à_signer_un_lot = + String::from_utf8_lossy(field.content).to_string(); + } + _ => (), + } + } + } + carte_ps +} + +#[cfg(test)] +mod test_decode_carte_ps { + use super::*; + + #[test] + fn test_francoise_pharmacien0052419() { + let bytes: &[u8] = &[ + 0, 1, 51, // 3 + 1, 48, // 2 + 1, 56, // 2 + 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12 + 1, 52, // 2 + 2, 50, 50, // 3 + 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18 + 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10 + 1, 84, // 2 + // total: 54 + 0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, + 1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, + 50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, + 48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, + ]; + let blocks = decode_ssv_memory(bytes, bytes.len()); + let carte_ps = decode_carte_ps(blocks); + + assert_eq!(carte_ps.titulaire.type_de_carte_ps, "0"); + assert_eq!(carte_ps.titulaire.type_d_identification_nationale, "8"); + assert_eq!( + carte_ps.titulaire.numero_d_identification_nationale, + "99700524194" + ); + assert_eq!( + carte_ps.titulaire.cle_du_numero_d_identification_nationale, + "4" + ); + assert_eq!(carte_ps.titulaire.code_civilite, "22"); + assert_eq!(carte_ps.titulaire.nom_du_ps, "PHARMACIEN0052419"); + assert_eq!(carte_ps.titulaire.prenom_du_ps, "FRANCOISE"); + assert_eq!(carte_ps.titulaire.categorie_carte, 'T'); + + assert_eq!(carte_ps.situations.len(), 1); + assert_eq!( + carte_ps.situations[0].numero_logique_de_la_situation_de_facturation_du_ps, + 1 + ); + assert_eq!(carte_ps.situations[0].mode_d_exercice, "0"); + assert_eq!(carte_ps.situations[0].statut_d_exercice, "1"); + assert_eq!(carte_ps.situations[0].secteur_d_activite, "86"); + assert_eq!(carte_ps.situations[0].type_d_identification_structure, "1"); + assert_eq!( + carte_ps.situations[0].numero_d_identification_structure, + "0B0221958" + ); + assert_eq!( + carte_ps.situations[0].cle_du_numero_d_identification_structure, + "8" + ); + assert_eq!( + carte_ps.situations[0].raison_sociale_structure, + "PHARMACIE DU CENTRE22195" + ); + assert_eq!( + carte_ps.situations[0].numero_d_identification_de_facturation_du_ps, + "00202419" + ); + assert_eq!( + carte_ps.situations[0].cle_du_numero_d_identification_de_facturation_du_ps, + "8" + ); + assert_eq!( + carte_ps.situations[0].numero_d_identification_du_ps_remplaçant, + "" + ); + assert_eq!( + carte_ps.situations[0].cle_du_numero_d_identification_du_ps_remplaçant, + "0" + ); + assert_eq!(carte_ps.situations[0].code_conventionnel, "1"); + assert_eq!(carte_ps.situations[0].code_specialite, "50"); + assert_eq!(carte_ps.situations[0].code_zone_tarifaire, "10"); + assert_eq!(carte_ps.situations[0].code_zone_ik, "00"); + assert_eq!(carte_ps.situations[0].code_agrement_1, "0"); + assert_eq!(carte_ps.situations[0].code_agrement_2, "0"); + assert_eq!(carte_ps.situations[0].code_agrement_3, "0"); + assert_eq!( + carte_ps.situations[0].habilitation_à_signer_une_facture, + "1" + ); + assert_eq!(carte_ps.situations[0].habilitation_à_signer_un_lot, "1"); + } +} diff --git a/crates/sesam-vitale/src/lib.rs b/crates/sesam-vitale/src/lib.rs index 7d12d9a..c575954 100644 --- a/crates/sesam-vitale/src/lib.rs +++ b/crates/sesam-vitale/src/lib.rs @@ -1,3 +1,8 @@ +pub mod cps; +pub mod libssv; +pub mod ssv_memory; +pub mod ssvlib_demo; + pub fn add(left: usize, right: usize) -> usize { left + right } diff --git a/crates/sesam-vitale/src/libssv.rs b/crates/sesam-vitale/src/libssv.rs index 7c3adf0..12686bd 100644 --- a/crates/sesam-vitale/src/libssv.rs +++ b/crates/sesam-vitale/src/libssv.rs @@ -1,17 +1,25 @@ /** * libssv.rs - * + * * Low level bindings to the SSVLIB dynamic library. * TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate */ - -use libc::{ c_char, c_void, c_ushort, size_t }; +use libc::{c_char, c_ushort, c_void, size_t}; #[cfg_attr(target_os = "linux", link(name = "ssvlux64"))] #[cfg_attr(target_os = "windows", link(name = "ssvw64"))] extern "C" { pub fn SSV_InitLIB2(pcRepSesamIni: *const c_char) -> c_ushort; - pub fn SSV_LireCartePS(NomRessourcePS: *const c_char, NomRessourceLecteur: *const c_char, CodePorteurPS: *const c_char, ZDonneesSortie: *mut *mut c_void, TTailleDonneesSortie: *mut size_t) -> c_ushort; - pub fn SSV_LireConfig(ZDonneesSortie: *mut *mut c_void, TTailleDonneesSortie: *mut size_t) -> c_ushort; + pub fn SSV_LireCartePS( + NomRessourcePS: *const c_char, + NomRessourceLecteur: *const c_char, + CodePorteurPS: *const c_char, + ZDonneesSortie: *mut *mut c_void, + TTailleDonneesSortie: *mut size_t, + ) -> c_ushort; + pub fn SSV_LireConfig( + ZDonneesSortie: *mut *mut c_void, + TTailleDonneesSortie: *mut size_t, + ) -> c_ushort; } /* TODO : replace void* by Rust struct : https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs */ diff --git a/crates/sesam-vitale/src/main.rs b/crates/sesam-vitale/src/main.rs index dfe2455..99ea99b 100644 --- a/crates/sesam-vitale/src/main.rs +++ b/crates/sesam-vitale/src/main.rs @@ -1,6 +1,7 @@ -mod ssvlib_demo; +mod cps; mod libssv; mod ssv_memory; +mod ssvlib_demo; fn main() { ssvlib_demo::demo(); diff --git a/crates/sesam-vitale/src/ssv_memory.rs b/crates/sesam-vitale/src/ssv_memory.rs index d00369a..c39be34 100644 --- a/crates/sesam-vitale/src/ssv_memory.rs +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -1,11 +1,11 @@ -/** - * Provide functions to manipulate raw memory from SSV library. - */ - -// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ? +/// # SSV Memory +/// Provide functions to manipulate raw memory from SSV library. +/// use std::convert::TryFrom; +// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ? + #[derive(PartialEq, Debug)] struct ElementSize { pub size: usize, @@ -16,11 +16,11 @@ impl TryFrom<&[u8]> for ElementSize { type Error = &'static str; fn try_from(bytes: &[u8]) -> Result { - /* Longueur: - * - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet - * - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur - */ - if bytes.len() == 0 { + /* Longueur: + * - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet + * - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur + */ + if bytes.is_empty() { return Err("Empty bytes input"); } @@ -41,7 +41,7 @@ impl TryFrom<&[u8]> for ElementSize { // u32::from_be_bytes() requires a 4 bytes array let mut padded_bytes = [0u8; 4]; - padded_bytes[size_bytes_len..].copy_from_slice(&size_bytes); + padded_bytes[size_bytes_len..].copy_from_slice(size_bytes); element_size.size = u32::from_be_bytes(padded_bytes) as usize; element_size.pad += size_bytes_len; @@ -62,7 +62,10 @@ impl<'a> From<&'a [u8]> for Block<'a> { let mut offset = 0; let id = u16::from_be_bytes(bytes[..2].try_into().unwrap()); offset += 2; - let ElementSize { size: block_size, pad } = bytes[2..].try_into().unwrap(); + let ElementSize { + size: block_size, + pad, + } = bytes[2..].try_into().unwrap(); offset += pad; let raw_content = &bytes[offset..]; let mut field_offset = 0; @@ -90,9 +93,9 @@ pub struct Field<'a> { impl<'a> From<&'a [u8]> for Field<'a> { fn from(bytes: &'a [u8]) -> Self { let ElementSize { size, pad } = bytes.try_into().unwrap(); - let contenu = &bytes[pad..pad+size]; + let contenu = &bytes[pad..pad + size]; Field { - size: pad+size, + size: pad + size, content: contenu, } } @@ -138,10 +141,7 @@ mod test_element_size { fn null_size() { let bytes: &[u8] = &[]; let result: Result = bytes.try_into(); - assert_eq!( - result, - Err("Empty bytes input"), - ); + assert_eq!(result, Err("Empty bytes input"),); } #[test] @@ -162,10 +162,7 @@ mod test_element_size { let bytes: &[u8] = &[0b_1000_0101_u8, 1, 1, 1, 1, 1]; let result: Result = bytes.try_into(); - assert_eq!( - result, - Err("Invalid memory: size is too big"), - ); + assert_eq!(result, Err("Invalid memory: size is too big"),); } } @@ -175,28 +172,27 @@ mod test_field { #[test] fn short_size() { - let bytes: &[u8] = &[51, - 1, 48, 1, 56, 11, 57, 57, 55, 48, 48, - 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, - 50, 17, 80, 72, 65, 82, 77, 65, 67, 73, - 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, - 70, 82, 65, 78, 67, 79, 73, 83, 69, 1, - 84, + let bytes: &[u8] = &[ + 51, 1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, 50, 17, + 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, 70, 82, 65, 78, + 67, 79, 73, 83, 69, 1, 84, ]; - let element: Field = bytes.try_into().unwrap(); + let element: Field = bytes.into(); assert_eq!(element.size, 52); assert_eq!(element.content[..5], [1, 48, 1, 56, 11]); } #[test] fn long_size() { - let mut bytes_vec = vec![0b_1000_0010_u8, - 0b_0000_0001_u8, 0b_0000_0000_u8, // size = 256 + let mut bytes_vec = vec![ + 0b_1000_0010_u8, + 0b_0000_0001_u8, + 0b_0000_0000_u8, // size = 256 ]; // Add 256 bytes to the content bytes_vec.append(&mut vec![1; 256]); let bytes: &[u8] = &bytes_vec; - let element: Field = bytes.try_into().unwrap(); + let element: Field = bytes.into(); assert_eq!(element.size, 259); assert_eq!(element.content.len(), 256); } @@ -208,12 +204,8 @@ mod test_block { #[test] fn test_francoise_pharmacien0052419_partial_block_1() { - let bytes: &[u8] = &[ - 1, 48, - 1, 56, - 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, - ]; - + let bytes: &[u8] = &[1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52]; + let field1: Field = bytes.into(); assert_eq!(field1.size, 2); assert_eq!(field1.content, &[48]); @@ -224,13 +216,16 @@ mod test_block { let field3: Field = bytes[field1.size + field2.size..].into(); assert_eq!(field3.size, 12); - assert_eq!(field3.content, &[57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52]); + assert_eq!( + field3.content, + &[57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52] + ); } #[test] fn test_francoise_pharmacien0052419() { let bytes: &[u8] = &[ - 0, 1, 51, // 3 + 0, 1, 51, // 3 1, 48, // 2 1, 56, // 2 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12 @@ -239,32 +234,13 @@ mod test_block { 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10 1, 84, // 2 - // total: 54 - - 0, 2, 83, - 1, 1, - 1, 48, - 1, 49, - 2, 56, 54, - 1, 49, - 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, - 1, 56, - 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, 57, 53, - 8, 48, 48, 50, 48, 50, 52, 49, 57, - 1, 56, - 0, - 1, 48, - 1, 49, - 2, 53, 48, - 2, 49, 48, - 2, 48, 48, - 1, 48, - 1, 48, - 1, 48, - 1, 49, - 1, 49, + // total: 54 + 0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, + 1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, + 50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, + 48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, ]; - + let first_block: Block = bytes.into(); assert_eq!(first_block.id, 1); assert_eq!(first_block.size, 54); @@ -284,7 +260,7 @@ mod test_decode_ssv_memory { #[test] fn test_francoise_pharmacien0052419() { let bytes: &[u8] = &[ - 0, 1, 51, // 3 + 0, 1, 51, // 3 1, 48, // 2 1, 56, // 2 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12 @@ -293,32 +269,13 @@ mod test_decode_ssv_memory { 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10 1, 84, // 2 - // total: 54 - - 0, 2, 83, - 1, 1, - 1, 48, - 1, 49, - 2, 56, 54, - 1, 49, - 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, - 1, 56, - 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, 57, 53, - 8, 48, 48, 50, 48, 50, 52, 49, 57, - 1, 56, - 0, - 1, 48, - 1, 49, - 2, 53, 48, - 2, 49, 48, - 2, 48, 48, - 1, 48, - 1, 48, - 1, 48, - 1, 49, - 1, 49, + // total: 54 + 0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, + 1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, + 50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, + 48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, ]; - let blocks = decode_ssv_memory(&bytes, bytes.len()); + let blocks = decode_ssv_memory(bytes, bytes.len()); assert_eq!(blocks.len(), 2); } -} \ No newline at end of file +} diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index 16b6e42..5ae1fbe 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -1,19 +1,19 @@ extern crate dotenv; +use libc::{c_void, size_t}; /** * High level API for the SSV library, * based on the low level bindings in libssv.rs. * */ -extern crate libc; - -use libc::{c_void, size_t}; -use std::ffi::CString; use std::path::PathBuf; + +use std::ffi::CString; use std::ptr; use std::env; -use crate::libssv::{SSV_InitLIB2, SSV_LireCartePS, SSV_LireConfig}; +use crate::cps::lire_carte; +use crate::libssv::{SSV_InitLIB2, SSV_LireConfig}; fn ssv_init_lib_2() { let ini_str = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); @@ -24,377 +24,6 @@ fn ssv_init_lib_2() { } } -/* -1. CB = Caractères Binaires » -2. CE = Caractères « Etendus » (ISO 8859-1) -3. CA = Caractères Alphanumériques (ASCII?) -4. CN = Caractères Numériques -*/ - -#[derive(Debug)] -struct CartePS { - titulaire: TitulairePS, - situations: Vec, -} -#[derive(Debug)] -struct TitulairePS { - type_de_carte_ps: String, // CN - type_d_identification_nationale: String, // CN - numero_d_identification_nationale: String, // CE - 8 -> 30 - cle_du_numero_d_identification_nationale: String, // CN - code_civilite: String, // CN - nom_du_ps: String, // CE - 27 - prenom_du_ps: String, // CE - 27 - categorie_carte: char, // CA -} - -#[derive(Debug)] -struct SituationPS { - numero_logique_de_la_situation_de_facturation_du_ps: u8, - mode_d_exercice: String, - statut_d_exercice: String, - secteur_d_activite: String, - type_d_identification_structure: String, - numero_d_identification_structure: String, - cle_du_numero_d_identification_structure: String, - raison_sociale_structure: String, - numero_d_identification_de_facturation_du_ps: String, - cle_du_numero_d_identification_de_facturation_du_ps: String, - numero_d_identification_du_ps_remplaçant: String, - cle_du_numero_d_identification_du_ps_remplaçant: String, - code_conventionnel: String, - code_specialite: String, - code_zone_tarifaire: String, - code_zone_ik: String, - code_agrement_1: String, - code_agrement_2: String, - code_agrement_3: String, - habilitation_à_signer_une_facture: String, - habilitation_à_signer_un_lot: String, -} - -impl Default for SituationPS { - fn default() -> Self { - Self { - numero_logique_de_la_situation_de_facturation_du_ps: 0, - mode_d_exercice: String::new(), - statut_d_exercice: String::new(), - secteur_d_activite: String::new(), - type_d_identification_structure: String::new(), - numero_d_identification_structure: String::new(), - cle_du_numero_d_identification_structure: String::new(), - raison_sociale_structure: String::new(), - numero_d_identification_de_facturation_du_ps: String::new(), - cle_du_numero_d_identification_de_facturation_du_ps: String::new(), - numero_d_identification_du_ps_remplaçant: String::new(), - cle_du_numero_d_identification_du_ps_remplaçant: String::new(), - code_conventionnel: String::new(), - code_specialite: String::new(), - code_zone_tarifaire: String::new(), - code_zone_ik: String::new(), - code_agrement_1: String::new(), - code_agrement_2: String::new(), - code_agrement_3: String::new(), - habilitation_à_signer_une_facture: String::new(), - habilitation_à_signer_un_lot: String::new(), - } - } -} - - - -fn ssv_lire_carte_ps(code_pin :&str, lecteur :&str) { - let resource_ps = - CString::new(lecteur).expect("CString::new failed"); - let resource_reader = CString::new("").expect("CString::new failed"); - let card_number = CString::new(code_pin).expect("CString::new failed"); - - let mut buffer: *mut c_void = ptr::null_mut(); - let mut size: size_t = 0; - let mut hex_values: &[u8] = &[]; - unsafe { - let result = SSV_LireCartePS( - resource_ps.as_ptr(), - resource_reader.as_ptr(), - card_number.as_ptr(), - &mut buffer, - &mut size, - ); - println!("SSV_LireCartePS result: {}", result); - - if !buffer.is_null() { - hex_values = std::slice::from_raw_parts(buffer as *const u8, size); - libc::free(buffer); - } - } - decode_zone_memoire(hex_values); - - /* IDÉE : implémenter decode_zone_memoire sous forme d'itérateur - for (group, group_rank) in decode_zone_memoire(...) { - for (field, field_rank) in group { - match group_rank { - GROUP_1: { - match field_rank { - 1 => carte_ps.titulaire.type_de_carte_ps = field.into(), - 2 => carte_ps.titulaire.type_d_identification_nationale = field.into(), - 3 => carte_ps.titulaire.numero_d_identification_nationale = field.into(), - 4 => carte_ps.titulaire.cle_du_numero_d_identification_nationale = field.into(), - 5 => carte_ps.titulaire.code_civilite = field.into(), - 6 => carte_ps.titulaire.nom_du_ps = field.into(), - 7 => carte_ps.titulaire.prenom_du_ps = field.into(), - 8 => carte_ps.titulaire.categorie_carte = field.into(), - _ => (), - } - } - GROUP_2: { - match field_rank { - ... - } - } - } - } - } - */ -} - -/** - * Options possibles : - * - Passer un objet de type variable (CartePS, CarteVitale...) et le remplir intelligemment - * - Retourner un itérateur et faire le remplissage en dehors de decode_zone_memoire - */ -pub fn decode_zone_memoire(bytes: &[u8]) { - // Maintenant, vous pouvez accéder aux octets individuels dans `donnees` - let mut current_pos_general = 0; - let mut current_groupe = 0; - let mut num_champ = 0; - - let mut carte_ps = CartePS { - titulaire: TitulairePS { - type_de_carte_ps: String::new(), - type_d_identification_nationale: String::new(), - numero_d_identification_nationale: String::new(), - cle_du_numero_d_identification_nationale: String::new(), - code_civilite: String::new(), - nom_du_ps: String::new(), - prenom_du_ps: String::new(), - categorie_carte: ' ', - }, - situations: Vec::new(), - }; - - while current_pos_general < bytes.len() - 1 { - num_champ = 0; - current_groupe = - 256 * bytes[current_pos_general] as i32 + bytes[current_pos_general + 1] as i32; - current_pos_general += 2; - - let longueur_groupe: usize; - if bytes[current_pos_general] < 128 { - longueur_groupe = bytes[current_pos_general] as usize; - } else { - let nbre_octets_longueur = bytes[current_pos_general] - 128; - longueur_groupe = (0..nbre_octets_longueur).fold(0, |acc, i| { - current_pos_general += 1; - acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize - }); - } - current_pos_general += 1; - - let mut current_pos_groupe = 0; - while current_pos_groupe < longueur_groupe { - num_champ += 1; - let longueur_champs: usize; - let nbre_octets_longueur: usize; - if bytes[current_pos_general] < 128 { - longueur_champs = bytes[current_pos_general] as usize; - nbre_octets_longueur = 1; - } else { - nbre_octets_longueur = bytes[current_pos_general] as usize - 128; - longueur_champs = (0..nbre_octets_longueur).fold(0, |acc, i| { - current_pos_general += 1; - current_pos_groupe += 1; - acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize - }); - } - - let mut bytes_champ = vec![0; longueur_champs]; - if longueur_champs > 0 { - bytes_champ.copy_from_slice( - &bytes[current_pos_general + nbre_octets_longueur - ..current_pos_general + nbre_octets_longueur + longueur_champs], - ); - - match current_groupe { - 1 => match num_champ { - 1 => { - carte_ps.titulaire.type_de_carte_ps = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 2 => { - carte_ps.titulaire.type_d_identification_nationale = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 3 => { - carte_ps.titulaire.numero_d_identification_nationale = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 4 => { - carte_ps.titulaire.cle_du_numero_d_identification_nationale = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 5 => { - carte_ps.titulaire.code_civilite = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 6 => { - carte_ps.titulaire.nom_du_ps = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 7 => { - carte_ps.titulaire.prenom_du_ps = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 8 => { - carte_ps.titulaire.categorie_carte = bytes_champ[0] as char; - } - _ => (), - }, - 2 => match num_champ { - 1 => { - carte_ps.situations.push(SituationPS::default()); - carte_ps - .situations - .last_mut() - .unwrap() - .numero_logique_de_la_situation_de_facturation_du_ps = - bytes_champ[0]; - } - 2 => { - carte_ps.situations.last_mut().unwrap().mode_d_exercice = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 3 => { - carte_ps.situations.last_mut().unwrap().statut_d_exercice = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 4 => { - carte_ps.situations.last_mut().unwrap().secteur_d_activite = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 5 => { - carte_ps - .situations - .last_mut() - .unwrap() - .type_d_identification_structure = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 6 => { - carte_ps - .situations - .last_mut() - .unwrap() - .numero_d_identification_structure = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 7 => { - carte_ps - .situations - .last_mut() - .unwrap() - .cle_du_numero_d_identification_structure = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 8 => { - carte_ps - .situations - .last_mut() - .unwrap() - .raison_sociale_structure = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 9 => { - carte_ps - .situations - .last_mut() - .unwrap() - .numero_d_identification_de_facturation_du_ps = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 10 => { - carte_ps - .situations - .last_mut() - .unwrap() - .cle_du_numero_d_identification_de_facturation_du_ps = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 11 => { - carte_ps - .situations - .last_mut() - .unwrap() - .numero_d_identification_du_ps_remplaçant = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 12 => { - carte_ps - .situations - .last_mut() - .unwrap() - .cle_du_numero_d_identification_du_ps_remplaçant = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 13 => { - carte_ps.situations.last_mut().unwrap().code_conventionnel = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 14 => { - carte_ps.situations.last_mut().unwrap().code_specialite = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 15 => { - carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 16 => { - carte_ps.situations.last_mut().unwrap().code_zone_ik = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 17 => { - carte_ps.situations.last_mut().unwrap().code_agrement_1 = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 18 => { - carte_ps.situations.last_mut().unwrap().code_agrement_2 = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 19 => { - carte_ps.situations.last_mut().unwrap().code_agrement_3 = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 20 => { - carte_ps.situations.last_mut().unwrap().habilitation_à_signer_une_facture = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - 21 => { - carte_ps.situations.last_mut().unwrap().habilitation_à_signer_un_lot = - String::from_utf8_lossy(&bytes_champ).to_string(); - } - _ => (), - }, - _ => (), - } - } - - current_pos_general += longueur_champs + nbre_octets_longueur; - current_pos_groupe += longueur_champs + nbre_octets_longueur; - } - } - - println!("CartePS: {:#?}", carte_ps); -} - fn ssv_lire_config() { let mut buffer: *mut c_void = ptr::null_mut(); let mut size: size_t = 0; @@ -428,185 +57,10 @@ pub fn demo() { ssv_init_lib_2(); let code_pin = "1234"; let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0"; - let carte_ps = ssv_lire_carte_ps2(code_pin, lecteur); + let carte_ps = lire_carte(code_pin, lecteur); println!("CartePS: {:#?}", carte_ps); - // ssv_lire_config(); + + ssv_lire_config(); println!("-----------------------------------------"); } - - -struct field{ - rank : i32, - value : Vec -} -impl Default for field { - fn default() -> Self { - Self {rank: 0, - value: Vec::new(), - } - } -} -pub struct group{ - rank : i32, - fields : Vec -} -impl Default for group { - fn default() -> Self { - Self {rank: 0, - fields: Vec::new(), - } - } -} - - pub fn decode_zone_memoire2(bytes: &[u8]) -> Vec{ - - let mut current_pos_general = 0; - let mut current_groupe = 0; - let mut num_champ = 0; - let mut longueur_groupe: usize; - let mut current_pos_groupe = 0; - let mut groups : Vec = Vec::new(); - - while current_pos_general < (bytes.len() - 1) { - num_champ = 0; - current_groupe = - 256 * bytes[current_pos_general] as i32 + bytes[current_pos_general + 1 ] as i32; - current_pos_general += 2; - - groups.push(group::default()); - groups.last_mut().unwrap().rank=current_groupe; - - if bytes[current_pos_general] < 128 { - longueur_groupe = bytes[current_pos_general] as usize; - } else { - let nbre_octets_longueur = bytes[current_pos_general] - 128; - longueur_groupe = (0..nbre_octets_longueur).fold(0, |acc, i| { - current_pos_general += 1; - acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize - }); - } - current_pos_general += 1; - println!("------- 3--------"); - current_pos_groupe = 0; - while current_pos_groupe < longueur_groupe { - num_champ += 1; - let longueur_champs: usize; - let nbre_octets_longueur: usize; - if bytes[current_pos_general] < 128 { - longueur_champs = bytes[current_pos_general] as usize; - nbre_octets_longueur = 1; - } else { - nbre_octets_longueur = bytes[current_pos_general] as usize - 128; - longueur_champs = (0..nbre_octets_longueur).fold(0, |acc, i| { - current_pos_general += 1; - current_pos_groupe += 1; - acc + (256_i32.pow(i as u32) * bytes[current_pos_general] as i32) as usize - }); - } - - groups.last_mut().unwrap().fields.push(field::default()); - groups.last_mut().unwrap().fields.last_mut().unwrap().rank=num_champ; - - if longueur_champs > 0 { - let mut bytes_champ = vec![0; longueur_champs]; - bytes_champ.copy_from_slice( - &bytes[current_pos_general + nbre_octets_longueur - ..current_pos_general + nbre_octets_longueur + longueur_champs], - ); - - //groups.last_mut().unwrap().fields.last_mut().unwrap().value= String::from_utf8_lossy(&bytes_champ).to_string(); - groups.last_mut().unwrap().fields.last_mut().unwrap().value= bytes_champ; - - - } - current_pos_general += longueur_champs + nbre_octets_longueur; - current_pos_groupe += longueur_champs + nbre_octets_longueur; - } - } - groups -} - -fn ssv_lire_carte_ps2(code_pin :&str, lecteur :&str) -> CartePS{ - let resource_ps = CString::new(lecteur).expect("CString::new failed"); - let resource_reader = CString::new("").expect("CString::new failed"); - let card_number = CString::new(code_pin).expect("CString::new failed"); - - let mut buffer: *mut c_void = ptr::null_mut(); - let mut size: size_t = 0; - let mut hex_values: &[u8] = &[]; - unsafe { - let result = SSV_LireCartePS( - resource_ps.as_ptr(), - resource_reader.as_ptr(), - card_number.as_ptr(), - &mut buffer, - &mut size, - ); - println!("SSV_LireCartePS result: {}", result); - - if !buffer.is_null() { - hex_values = std::slice::from_raw_parts(buffer as *const u8, size); - libc::free(buffer); - } - } - let groups= decode_zone_memoire2(hex_values); - let mut carte_ps = CartePS { - titulaire: TitulairePS { - type_de_carte_ps: String::new(), - type_d_identification_nationale: String::new(), - numero_d_identification_nationale: String::new(), - cle_du_numero_d_identification_nationale: String::new(), - code_civilite: String::new(), - nom_du_ps: String::new(), - prenom_du_ps: String::new(), - categorie_carte: ' ', - }, - situations: Vec::new(), -}; -for item_group in groups.iter(){ - for item_field in item_group.fields.iter(){ - match (item_group.rank,item_field.rank) { - (1, 1) => {carte_ps.titulaire.type_de_carte_ps = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 2) => {carte_ps.titulaire.type_d_identification_nationale = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 3) => {carte_ps.titulaire.numero_d_identification_nationale = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 4) => {carte_ps.titulaire.cle_du_numero_d_identification_nationale = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 5) => {carte_ps.titulaire.code_civilite = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 6) => {carte_ps.titulaire.nom_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 7) => {carte_ps.titulaire.prenom_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} - (1, 8) => unsafe {carte_ps.titulaire.categorie_carte = char::from_u32_unchecked(item_field.value[0] as u32)} - (2, 1) => {carte_ps.situations.push(SituationPS::default()); - carte_ps.situations.last_mut().unwrap().numero_logique_de_la_situation_de_facturation_du_ps = item_field.value[0];} - (2, 2) => {carte_ps.situations.last_mut().unwrap().mode_d_exercice = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 3) => {carte_ps.situations.last_mut().unwrap().statut_d_exercice = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 4) => {carte_ps.situations.last_mut().unwrap().secteur_d_activite = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 5) => {carte_ps.situations.last_mut().unwrap().type_d_identification_structure = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 6) => {carte_ps.situations.last_mut().unwrap().numero_d_identification_structure = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 7) => {carte_ps.situations.last_mut().unwrap().cle_du_numero_d_identification_structure = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 8) => {carte_ps.situations.last_mut().unwrap().raison_sociale_structure = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 9) => {carte_ps.situations.last_mut().unwrap().numero_d_identification_de_facturation_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 10) => {carte_ps.situations.last_mut().unwrap().cle_du_numero_d_identification_de_facturation_du_ps = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 11) => {carte_ps.situations.last_mut().unwrap().numero_d_identification_du_ps_remplaçant = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 12) => {carte_ps.situations.last_mut().unwrap().cle_du_numero_d_identification_du_ps_remplaçant = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 13) => {carte_ps.situations.last_mut().unwrap().code_conventionnel = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 14) => {carte_ps.situations.last_mut().unwrap().code_specialite = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 15) => {carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 16) => {carte_ps.situations.last_mut().unwrap().code_zone_ik = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 17) => {carte_ps.situations.last_mut().unwrap().code_agrement_1 = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 18) => {carte_ps.situations.last_mut().unwrap().code_agrement_2 = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 19) => {carte_ps.situations.last_mut().unwrap().code_agrement_3 = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 20) => {carte_ps.situations.last_mut().unwrap().habilitation_à_signer_une_facture = String::from_utf8_lossy(&item_field.value).to_string();} - (2, 21) => {carte_ps.situations.last_mut().unwrap().habilitation_à_signer_un_lot = String::from_utf8_lossy(&item_field.value).to_string();} - _ => (), - } - } -} -carte_ps - -} - - - - - - From 723c06acd998d9770132f0d37b19e6d69c9e9fab Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Fri, 2 Aug 2024 22:58:32 +0200 Subject: [PATCH 08/11] refactor: clean comments and docstring --- crates/sesam-vitale/src/cps.rs | 40 ++++++++++++-------------- crates/sesam-vitale/src/libssv.rs | 12 ++++---- crates/sesam-vitale/src/main.rs | 1 - crates/sesam-vitale/src/ssv_memory.rs | 13 ++++----- crates/sesam-vitale/src/ssvlib_demo.rs | 24 ++++++---------- 5 files changed, 38 insertions(+), 52 deletions(-) diff --git a/crates/sesam-vitale/src/cps.rs b/crates/sesam-vitale/src/cps.rs index f332265..d9ed7c8 100644 --- a/crates/sesam-vitale/src/cps.rs +++ b/crates/sesam-vitale/src/cps.rs @@ -2,13 +2,6 @@ use libc::{c_void, size_t}; use std::ffi::CString; use std::ptr; -/* -1. CB = Caractères Binaires » -2. CE = Caractères « Etendus » (ISO 8859-1) -3. CA = Caractères Alphanumériques (ASCII?) -4. CN = Caractères Numériques -*/ - use crate::libssv::SSV_LireCartePS; use crate::ssv_memory::{decode_ssv_memory, Block}; @@ -18,6 +11,10 @@ pub struct CartePS { situations: Vec, } +// 1. CB = Caractères Binaires » +// 2. CE = Caractères « Etendus » (ISO 8859-1) +// 3. CA = Caractères Alphanumériques (ASCII?) +// 4. CN = Caractères Numériques #[derive(Debug, Default)] struct TitulairePS { type_de_carte_ps: String, // CN @@ -261,20 +258,21 @@ mod test_decode_carte_ps { #[test] fn test_francoise_pharmacien0052419() { let bytes: &[u8] = &[ - 0, 1, 51, // 3 - 1, 48, // 2 - 1, 56, // 2 - 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12 - 1, 52, // 2 - 2, 50, 50, // 3 - 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18 - 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10 - 1, 84, // 2 - // total: 54 - 0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, - 1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, - 50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, - 48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, + 0, 1, 51, // Block 01, Content size 51 + 1, 48, // Field 01, Content size 1 + 1, 56, // Field 02, Content size 1 + 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // Field 03, Content size 11 + 1, 52, // Field 04, Content size 1 + 2, 50, 50, // Field 05, Content size 2 + 17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, + 57, // Field 06, Content size 17 + 9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // Field 07, Content size 9 + 1, 84, // Field 08, Content size 1 + 0, 2, 83, // Block 02, Content size 83 + 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24, + 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, + 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49, + 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, ]; let blocks = decode_ssv_memory(bytes, bytes.len()); let carte_ps = decode_carte_ps(blocks); diff --git a/crates/sesam-vitale/src/libssv.rs b/crates/sesam-vitale/src/libssv.rs index 12686bd..22ed690 100644 --- a/crates/sesam-vitale/src/libssv.rs +++ b/crates/sesam-vitale/src/libssv.rs @@ -1,9 +1,7 @@ -/** - * libssv.rs - * - * Low level bindings to the SSVLIB dynamic library. - * TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate - */ +/// libssv.rs +/// +/// Low level bindings to the SSVLIB dynamic library. +// TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate use libc::{c_char, c_ushort, c_void, size_t}; #[cfg_attr(target_os = "linux", link(name = "ssvlux64"))] @@ -22,4 +20,4 @@ extern "C" { TTailleDonneesSortie: *mut size_t, ) -> c_ushort; } -/* TODO : replace void* by Rust struct : https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs */ +// TODO : replace void* by Rust struct : https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs diff --git a/crates/sesam-vitale/src/main.rs b/crates/sesam-vitale/src/main.rs index 99ea99b..479f7f0 100644 --- a/crates/sesam-vitale/src/main.rs +++ b/crates/sesam-vitale/src/main.rs @@ -5,5 +5,4 @@ mod ssvlib_demo; fn main() { ssvlib_demo::demo(); - // XXX } diff --git a/crates/sesam-vitale/src/ssv_memory.rs b/crates/sesam-vitale/src/ssv_memory.rs index c39be34..cfd1e68 100644 --- a/crates/sesam-vitale/src/ssv_memory.rs +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -1,30 +1,27 @@ /// # SSV Memory /// Provide functions to manipulate raw memory from SSV library. -/// - use std::convert::TryFrom; -// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ? - #[derive(PartialEq, Debug)] struct ElementSize { pub size: usize, pub pad: usize, } +// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ? + impl TryFrom<&[u8]> for ElementSize { type Error = &'static str; fn try_from(bytes: &[u8]) -> Result { - /* Longueur: - * - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet - * - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur - */ if bytes.is_empty() { return Err("Empty bytes input"); } let mut element_size = ElementSize { size: 0, pad: 1 }; + // Longueur: + // - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet + // - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur if bytes[0] & 0b1000_0000 == 0 { // Size coded on 1 byte element_size.size = bytes[0] as usize; diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index 5ae1fbe..42026e8 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -1,16 +1,11 @@ +/// High level API for the SSV library, +/// based on the low level bindings in libssv.rs. extern crate dotenv; use libc::{c_void, size_t}; -/** - * High level API for the SSV library, - * based on the low level bindings in libssv.rs. - * - */ -use std::path::PathBuf; - -use std::ffi::CString; -use std::ptr; - use std::env; +use std::ffi::CString; +use std::path::PathBuf; +use std::ptr; use crate::cps::lire_carte; use crate::libssv::{SSV_InitLIB2, SSV_LireConfig}; @@ -44,17 +39,16 @@ fn ssv_lire_config() { } pub fn demo() { - /* - * TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env` - * Maybe we could use a system standard config path to store a config file - */ + // TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env` + // Maybe we could use a system standard config path to store a config file let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let manifest_path = PathBuf::from(manifest_dir); dotenv::from_path(manifest_path.join(".env")).ok(); - println!("------- Demo for the SSV library 2 --------"); + println!("------- Demo for the SSV library --------"); ssv_init_lib_2(); + let code_pin = "1234"; let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0"; let carte_ps = lire_carte(code_pin, lecteur); From 4d004afc5eb6e5757f423f5919e72228accd16e3 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Fri, 2 Aug 2024 23:00:44 +0200 Subject: [PATCH 09/11] feature: handle some errors in decode_carte_ps --- crates/sesam-vitale/src/cps.rs | 33 ++++++++++++++++++++++---- crates/sesam-vitale/src/ssvlib_demo.rs | 2 +- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/sesam-vitale/src/cps.rs b/crates/sesam-vitale/src/cps.rs index d9ed7c8..abd43eb 100644 --- a/crates/sesam-vitale/src/cps.rs +++ b/crates/sesam-vitale/src/cps.rs @@ -52,7 +52,7 @@ struct SituationPS { habilitation_à_signer_un_lot: String, } -pub fn lire_carte(code_pin: &str, lecteur: &str) -> CartePS { +pub fn lire_carte(code_pin: &str, lecteur: &str) -> Result { let resource_ps = CString::new(lecteur).expect("CString::new failed"); let resource_reader = CString::new("").expect("CString::new failed"); let card_number = CString::new(code_pin).expect("CString::new failed"); @@ -79,7 +79,7 @@ pub fn lire_carte(code_pin: &str, lecteur: &str) -> CartePS { decode_carte_ps(groups) } -fn decode_carte_ps(groups: Vec) -> CartePS { +fn decode_carte_ps(groups: Vec) -> Result { let mut carte_ps = CartePS::default(); for group in groups { for (field_index, field) in group.content.iter().enumerate() { @@ -244,11 +244,16 @@ fn decode_carte_ps(groups: Vec) -> CartePS { .habilitation_à_signer_un_lot = String::from_utf8_lossy(field.content).to_string(); } - _ => (), + _ => { + return Err(format!( + "Unknown (group, field) pair: ({}, {})", + group.id, field.id + )) + } } } } - carte_ps + Ok(carte_ps) } #[cfg(test)] @@ -275,7 +280,7 @@ mod test_decode_carte_ps { 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, ]; let blocks = decode_ssv_memory(bytes, bytes.len()); - let carte_ps = decode_carte_ps(blocks); + let carte_ps = decode_carte_ps(blocks).unwrap(); assert_eq!(carte_ps.titulaire.type_de_carte_ps, "0"); assert_eq!(carte_ps.titulaire.type_d_identification_nationale, "8"); @@ -342,4 +347,22 @@ mod test_decode_carte_ps { ); assert_eq!(carte_ps.situations[0].habilitation_à_signer_un_lot, "1"); } + + #[test] + #[should_panic] + fn test_missing_field() { + todo!(); + } + + #[test] + #[should_panic] + fn test_unknown_group_field_pair() { + todo!(); + } + + #[test] + #[should_panic] + fn test_invalid_field_format() { + todo!(); + } } diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index 42026e8..b9ee114 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -51,7 +51,7 @@ pub fn demo() { let code_pin = "1234"; let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0"; - let carte_ps = lire_carte(code_pin, lecteur); + let carte_ps = lire_carte(code_pin, lecteur).unwrap(); println!("CartePS: {:#?}", carte_ps); ssv_lire_config(); From 9126d1311b3de719d866c1b149b73addb49965eb Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Fri, 2 Aug 2024 23:02:21 +0200 Subject: [PATCH 10/11] feat: add a field.id --- crates/sesam-vitale/src/cps.rs | 4 ++-- crates/sesam-vitale/src/ssv_memory.rs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/sesam-vitale/src/cps.rs b/crates/sesam-vitale/src/cps.rs index abd43eb..b9025f9 100644 --- a/crates/sesam-vitale/src/cps.rs +++ b/crates/sesam-vitale/src/cps.rs @@ -82,8 +82,8 @@ pub fn lire_carte(code_pin: &str, lecteur: &str) -> Result { fn decode_carte_ps(groups: Vec) -> Result { let mut carte_ps = CartePS::default(); for group in groups { - for (field_index, field) in group.content.iter().enumerate() { - match (group.id, field_index + 1) { + for field in group.content { + match (group.id, field.id) { (1, 1) => { carte_ps.titulaire.type_de_carte_ps = String::from_utf8_lossy(field.content).to_string(); diff --git a/crates/sesam-vitale/src/ssv_memory.rs b/crates/sesam-vitale/src/ssv_memory.rs index cfd1e68..fba08af 100644 --- a/crates/sesam-vitale/src/ssv_memory.rs +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -68,9 +68,12 @@ impl<'a> From<&'a [u8]> for Block<'a> { let mut field_offset = 0; // While there is still content to read, parse Fields let mut content = Vec::new(); + let mut field_id = 1; while field_offset < block_size { - let field: Field<'a> = raw_content[field_offset..].into(); + let mut field: Field<'a> = raw_content[field_offset..].into(); + field.id = field_id; field_offset += field.size; + field_id += 1; content.push(field); } Block { @@ -83,6 +86,7 @@ impl<'a> From<&'a [u8]> for Block<'a> { #[derive(Debug)] pub struct Field<'a> { + pub id: u16, pub size: usize, pub content: &'a [u8], } @@ -92,6 +96,7 @@ impl<'a> From<&'a [u8]> for Field<'a> { let ElementSize { size, pad } = bytes.try_into().unwrap(); let contenu = &bytes[pad..pad + size]; Field { + id: 0, size: pad + size, content: contenu, } From 83aef34750008d35bc3aa1bb010b86d8e9b7abdd Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Fri, 2 Aug 2024 23:02:44 +0200 Subject: [PATCH 11/11] fix: handle multiple situations on a CPS --- crates/sesam-vitale/src/cps.rs | 82 +++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/crates/sesam-vitale/src/cps.rs b/crates/sesam-vitale/src/cps.rs index b9025f9..9cb7782 100644 --- a/crates/sesam-vitale/src/cps.rs +++ b/crates/sesam-vitale/src/cps.rs @@ -116,7 +116,7 @@ fn decode_carte_ps(groups: Vec) -> Result { let byte = field.content[0]; carte_ps.titulaire.categorie_carte = byte as char; } - (2, 1) => { + (2..=16, 1) => { carte_ps.situations.push(SituationPS::default()); carte_ps .situations @@ -124,19 +124,19 @@ fn decode_carte_ps(groups: Vec) -> Result { .unwrap() .numero_logique_de_la_situation_de_facturation_du_ps = field.content[0]; } - (2, 2) => { + (2..=16, 2) => { carte_ps.situations.last_mut().unwrap().mode_d_exercice = String::from_utf8_lossy(field.content).to_string(); } - (2, 3) => { + (2..=16, 3) => { carte_ps.situations.last_mut().unwrap().statut_d_exercice = String::from_utf8_lossy(field.content).to_string(); } - (2, 4) => { + (2..=16, 4) => { carte_ps.situations.last_mut().unwrap().secteur_d_activite = String::from_utf8_lossy(field.content).to_string(); } - (2, 5) => { + (2..=16, 5) => { carte_ps .situations .last_mut() @@ -144,7 +144,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .type_d_identification_structure = String::from_utf8_lossy(field.content).to_string(); } - (2, 6) => { + (2..=16, 6) => { carte_ps .situations .last_mut() @@ -152,7 +152,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .numero_d_identification_structure = String::from_utf8_lossy(field.content).to_string(); } - (2, 7) => { + (2..=16, 7) => { carte_ps .situations .last_mut() @@ -160,7 +160,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .cle_du_numero_d_identification_structure = String::from_utf8_lossy(field.content).to_string(); } - (2, 8) => { + (2..=16, 8) => { carte_ps .situations .last_mut() @@ -168,7 +168,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .raison_sociale_structure = String::from_utf8_lossy(field.content).to_string(); } - (2, 9) => { + (2..=16, 9) => { carte_ps .situations .last_mut() @@ -176,7 +176,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .numero_d_identification_de_facturation_du_ps = String::from_utf8_lossy(field.content).to_string(); } - (2, 10) => { + (2..=16, 10) => { carte_ps .situations .last_mut() @@ -184,7 +184,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .cle_du_numero_d_identification_de_facturation_du_ps = String::from_utf8_lossy(field.content).to_string(); } - (2, 11) => { + (2..=16, 11) => { carte_ps .situations .last_mut() @@ -192,7 +192,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .numero_d_identification_du_ps_remplaçant = String::from_utf8_lossy(field.content).to_string(); } - (2, 12) => { + (2..=16, 12) => { carte_ps .situations .last_mut() @@ -200,35 +200,35 @@ fn decode_carte_ps(groups: Vec) -> Result { .cle_du_numero_d_identification_du_ps_remplaçant = String::from_utf8_lossy(field.content).to_string(); } - (2, 13) => { + (2..=16, 13) => { carte_ps.situations.last_mut().unwrap().code_conventionnel = String::from_utf8_lossy(field.content).to_string(); } - (2, 14) => { + (2..=16, 14) => { carte_ps.situations.last_mut().unwrap().code_specialite = String::from_utf8_lossy(field.content).to_string(); } - (2, 15) => { + (2..=16, 15) => { carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = String::from_utf8_lossy(field.content).to_string(); } - (2, 16) => { + (2..=16, 16) => { carte_ps.situations.last_mut().unwrap().code_zone_ik = String::from_utf8_lossy(field.content).to_string(); } - (2, 17) => { + (2..=16, 17) => { carte_ps.situations.last_mut().unwrap().code_agrement_1 = String::from_utf8_lossy(field.content).to_string(); } - (2, 18) => { + (2..=16, 18) => { carte_ps.situations.last_mut().unwrap().code_agrement_2 = String::from_utf8_lossy(field.content).to_string(); } - (2, 19) => { + (2..=16, 19) => { carte_ps.situations.last_mut().unwrap().code_agrement_3 = String::from_utf8_lossy(field.content).to_string(); } - (2, 20) => { + (2..=16, 20) => { carte_ps .situations .last_mut() @@ -236,7 +236,7 @@ fn decode_carte_ps(groups: Vec) -> Result { .habilitation_à_signer_une_facture = String::from_utf8_lossy(field.content).to_string(); } - (2, 21) => { + (2..=16, 21) => { carte_ps .situations .last_mut() @@ -348,6 +348,46 @@ mod test_decode_carte_ps { assert_eq!(carte_ps.situations[0].habilitation_à_signer_un_lot, "1"); } + #[test] + fn test_multiple_situations() { + let bytes: &[u8] = &[ + 0, 1, 51, // Block 01, Content size 51 + 1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, 50, 17, 80, + 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, 70, 82, 65, 78, 67, + 79, 73, 83, 69, 1, 84, 0, 2, 83, // Block 02, Content size 83 + 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24, + 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, + 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49, + 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 0, 3, + 83, // Block 03, Content size 83 + 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24, + 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, + 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49, + 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 0, 4, + 83, // Block 04, Content size 83 + 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24, + 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49, + 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49, + 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, + ]; + let blocks = decode_ssv_memory(bytes, bytes.len()); + let carte_ps = decode_carte_ps(blocks).unwrap(); + + assert_eq!(carte_ps.situations.len(), 3); + assert_eq!( + carte_ps.situations[0].raison_sociale_structure, + "PHARMACIE DU CENTRE22195" + ); + assert_eq!( + carte_ps.situations[1].raison_sociale_structure, + "PHARMACIE DU CENTRE22195" + ); + assert_eq!( + carte_ps.situations[2].raison_sociale_structure, + "PHARMACIE DU CENTRE22195" + ); + } + #[test] #[should_panic] fn test_missing_field() {