diff --git a/crates/sesam-vitale/src/cps.rs b/crates/sesam-vitale/src/cps.rs new file mode 100644 index 0000000..9cb7782 --- /dev/null +++ b/crates/sesam-vitale/src/cps.rs @@ -0,0 +1,408 @@ +use libc::{c_void, size_t}; +use std::ffi::CString; +use std::ptr; + +use crate::libssv::SSV_LireCartePS; +use crate::ssv_memory::{decode_ssv_memory, Block}; + +#[derive(Debug, Default)] +pub struct CartePS { + titulaire: TitulairePS, + 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 + 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) -> 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"); + + 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) -> Result { + let mut carte_ps = CartePS::default(); + for group in groups { + 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(); + } + (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..=16, 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..=16, 2) => { + carte_ps.situations.last_mut().unwrap().mode_d_exercice = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 3) => { + carte_ps.situations.last_mut().unwrap().statut_d_exercice = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 4) => { + carte_ps.situations.last_mut().unwrap().secteur_d_activite = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 5) => { + carte_ps + .situations + .last_mut() + .unwrap() + .type_d_identification_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 6) => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 7) => { + carte_ps + .situations + .last_mut() + .unwrap() + .cle_du_numero_d_identification_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 8) => { + carte_ps + .situations + .last_mut() + .unwrap() + .raison_sociale_structure = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 9) => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_de_facturation_du_ps = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 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..=16, 11) => { + carte_ps + .situations + .last_mut() + .unwrap() + .numero_d_identification_du_ps_remplaçant = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 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..=16, 13) => { + carte_ps.situations.last_mut().unwrap().code_conventionnel = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 14) => { + carte_ps.situations.last_mut().unwrap().code_specialite = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 15) => { + carte_ps.situations.last_mut().unwrap().code_zone_tarifaire = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 16) => { + carte_ps.situations.last_mut().unwrap().code_zone_ik = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 17) => { + carte_ps.situations.last_mut().unwrap().code_agrement_1 = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 18) => { + carte_ps.situations.last_mut().unwrap().code_agrement_2 = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 19) => { + carte_ps.situations.last_mut().unwrap().code_agrement_3 = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 20) => { + carte_ps + .situations + .last_mut() + .unwrap() + .habilitation_à_signer_une_facture = + String::from_utf8_lossy(field.content).to_string(); + } + (2..=16, 21) => { + carte_ps + .situations + .last_mut() + .unwrap() + .habilitation_à_signer_un_lot = + String::from_utf8_lossy(field.content).to_string(); + } + _ => { + return Err(format!( + "Unknown (group, field) pair: ({}, {})", + group.id, field.id + )) + } + } + } + } + Ok(carte_ps) +} + +#[cfg(test)] +mod test_decode_carte_ps { + use super::*; + + #[test] + fn test_francoise_pharmacien0052419() { + let bytes: &[u8] = &[ + 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).unwrap(); + + 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"); + } + + #[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() { + 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/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..22ed690 100644 --- a/crates/sesam-vitale/src/libssv.rs +++ b/crates/sesam-vitale/src/libssv.rs @@ -1,17 +1,23 @@ -/** - * 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 }; +/// 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"))] #[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 */ +// 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 b863fbb..479f7f0 100644 --- a/crates/sesam-vitale/src/main.rs +++ b/crates/sesam-vitale/src/main.rs @@ -1,5 +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 new file mode 100644 index 0000000..fba08af --- /dev/null +++ b/crates/sesam-vitale/src/ssv_memory.rs @@ -0,0 +1,283 @@ +/// # SSV Memory +/// Provide functions to manipulate raw memory from SSV library. +use std::convert::TryFrom; + +#[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 { + 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; + } 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]; + + // 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) + } +} + +#[derive(Debug)] +pub struct Block<'a> { + pub id: u16, + pub size: usize, + pub content: Vec>, +} + +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(); + let mut field_id = 1; + while field_offset < block_size { + 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 { + id, + size: offset + block_size, + content, + } + } +} + +#[derive(Debug)] +pub struct Field<'a> { + pub id: u16, + 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 { + id: 0, + 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_element_size { + use super::*; + + #[test] + fn short_size() { + 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: &[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: &[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: &[u8] = &[]; + let result: Result = bytes.try_into(); + assert_eq!(result, Err("Empty bytes input"),); + } + + #[test] + fn invalid_memory() { + let bytes: &[u8] = &[0b_1000_0001_u8]; + let result: Result = bytes.try_into(); + assert_eq!( + result, + Err("Invalid memory: not enough bytes to read the size"), + ); + + let bytes: &[u8] = &[0b_1000_0010_u8, 1]; + let result: Result = bytes.try_into(); + assert_eq!( + result, + Err("Invalid memory: not enough bytes to read the 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"),); + } +} + +#[cfg(test)] +mod test_field { + use super::*; + + #[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 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 + ]; + // Add 256 bytes to the content + bytes_vec.append(&mut vec![1; 256]); + let bytes: &[u8] = &bytes_vec; + let element: Field = bytes.into(); + 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); + } +} diff --git a/crates/sesam-vitale/src/ssvlib_demo.rs b/crates/sesam-vitale/src/ssvlib_demo.rs index a8f4731..b9ee114 100644 --- a/crates/sesam-vitale/src/ssvlib_demo.rs +++ b/crates/sesam-vitale/src/ssvlib_demo.rs @@ -1,24 +1,14 @@ -/** - * High level API for the SSV library, - * based on the low level bindings in libssv.rs. - * - */ - -extern crate libc; +/// 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 }; +use libc::{c_void, size_t}; +use std::env; 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::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"); @@ -29,35 +19,6 @@ fn ssv_init_lib_2() { } } -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 mut buffer: *mut c_void = ptr::null_mut(); - let mut size: size_t = 0; - 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() { - let hex_values = std::slice::from_raw_parts(buffer as *const u8, size); - for &byte in hex_values { - print!("{:02X} ", byte); - } - println!(); - - libc::free(buffer); - } - } -} - fn ssv_lire_config() { let mut buffer: *mut c_void = ptr::null_mut(); let mut size: size_t = 0; @@ -78,10 +39,8 @@ 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(); @@ -89,7 +48,12 @@ pub fn demo() { println!("------- Demo for the SSV library --------"); 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 = lire_carte(code_pin, lecteur).unwrap(); + println!("CartePS: {:#?}", carte_ps); + ssv_lire_config(); println!("-----------------------------------------");