23 Commits

Author SHA1 Message Date
2ad6dba535 feat: implement high level get_vitale_card_rights over SSV_LireDroitsVitale - without output data parsing 2024-10-24 21:59:04 +02:00
db5eedd45a feat: Ajout de la fonction SSV_LireDroitsVitale à la crate fsv-sys et refactoring des tests unitaires associés 2024-10-24 21:59:04 +02:00
e01b28f66f feat: add integration tests with snapshot for some CPS cards in kits 2024-10-24 21:59:03 +02:00
51bed2b4f4 feat: implement group2, CPS situation for SSV_LireCartePS 2024-10-24 21:59:03 +02:00
76d9e4e4f0 feat: Une enums for deserializable fields in CPS Holder group (01) 2024-10-24 21:59:03 +02:00
0b5b4978a6 feat: Take the size into account for Block reading, to handle missing fields 2024-10-24 21:59:02 +02:00
8fe733fc30 feat: use an enum instead of raw ID for CPS type 2024-10-24 21:59:02 +02:00
71f9d0c5f8 feat: raw (non-deserialized) implementation of the 01 group of LireCartePS 2024-10-24 21:59:02 +02:00
0ef8ede764 feat: implement a full SSV_LireConfig output parsing, using deku for a declarative bytes parsing
Co-authored-by: theo <t.lettermann@criteo.com>
2024-10-24 21:59:01 +02:00
ae68de8a3c Merge pull request 'feat: Update avatar URLs in Avatar and LoginModal components' (#68) from fix/change_default_avatar_urls into main
Le service que j'avais utilisé pour générer des avatars aléatoires pour cette démo a cessé de fonctionner aujourd'hui ... XD

Je le remplace donc par un autre service ^^

Reviewed-on: P4Pillon/Krys4lide#68
Reviewed-by: kosssi <simon@p4pillon.org>
2024-10-14 17:11:13 +02:00
e24b9c7859 feat: Update avatar URLs in Avatar and LoginModal components 2024-10-14 17:10:46 +02:00
c83824ae34 Merge pull request 'Initialisation de la crate FSV, couche haut-niveau des accès aux fonctions SSV' (#71) from 38-fsv-high-level-lib into main
### Détails

- Création de la crate fsv, couche de haut niveau pour l'usage des librairies FSV
- Implémentation partielle des appels "haut niveau" aux fonctions SSV_InitLIB2, SSV_LireCartePS et SSV_LireConfig
- Implémentation de la gestion des erreurs numériques de la librairie C pour ces fonctions

### Pourquoi ?

L'usage est de séparer les couches bas niveau, exposant des versions légèrement "rustifiées" des appels aux fonctions des librairies C, des couches "haut niveau", garantissant un usage "safe" et "user friendly".

Contribue à #38
Closes #49

Reviewed-on: P4Pillon/Krys4lide#71
Reviewed-by: kosssi <simon@p4pillon.org>
2024-10-09 22:46:41 +02:00
1b94fefad3 fixup! feat: implémentation partielle de la fonction get_config et de ses erreurs 2024-10-09 22:45:01 +02:00
5f7229c307 fixup! feat: Implémentation de la gestion des erreurs numériques de la librairie C pour la fonction InitLIB2 2024-10-09 22:43:25 +02:00
d043915a29 feat: implémentation partielle de la fonction get_config et de ses erreurs 2024-10-09 22:38:53 +02:00
2260b0cfa8 feat: implement LireCartePS with hardcoded reader and all errors 2024-10-09 22:38:53 +02:00
203521fe01 feat: Implémentation de la gestion des erreurs numériques de la librairie C pour la fonction InitLIB2
Co-authored-by: theo <t.lettermann@criteo.com>
2024-10-09 22:38:53 +02:00
3c1e691cb8 feat: Création de la crate fsv, couche de haut niveau pour l'usage des librairies FSV 2024-10-09 22:38:53 +02:00
add40f32c5 Merge pull request 'Création d'une sys-crate pour la gestion des librairies FSV' (#70) from feat/38-fsv-sys-crate into main
### Détails

Début d'implémentation de bindings pour FSV SESAM-Vitale

- Création de la crates/fsv-sys
- Ajout des headers des versions FSV 1.40.14 et 1.40.13 dans un sous-dossier crates/fsv-sys/vendor
- Génération des bindings depuis ces headers avec bindgen
- Implémentation d'une structure de loading de la librairie au runtime
    - Un pattern similaire au "TypeState pattern" est utilisé pour gérer plusieurs versions possibles des bindings FSV
    - Une macro permet de générer avec un peu moins de boilerplate que nécessaire la couche d'accès aux fonctions de la librairie

### Pourquoi ?

Cette PR est une étape importante du ticket #38

Une telle sys-crate, respectant (à peu près) les bonnes pratiques d'une telle implem, permet :
- Pouvoir être diffusée publiquement en tant que telle, pour faciliter le travail à des copaines
- De concentrer le travail très spécifique et bas niveau de gestion des librairies et des bindings dans une crate dédiée
- De ne pas "forcer" une approche "orientée" sur l'API plus haut niveau qu'on décide de brancher sur ces bindings
    - En effet, l'implem haut niveau fait des choix non neutres, comme le choix de certains types, la technique de gestion des erreurs, etc.

### Documentation

# Aide reçue sur les forums Rust
- [Génération des bindings avec Bindgen](https://users.rust-lang.org/t/how-to-handle-bindgen-generating-types-aliases-instead-of-callable-functions/118083)
- [Gestion des versions multiples avec la structure de loading de la librairie](https://users.rust-lang.org/t/manage-various-versions-of-a-c-library-loaded-at-runtime/118973)

# Documentations
- [Making a sys-crate](https://kornel.ski/rust-sys-crate)
- [LibLoading](https://docs.rs/libloading/latest/libloading/)
- [Bindgen](https://rust-lang.github.io/rust-bindgen/)

Reviewed-on: P4Pillon/Krys4lide#70
Reviewed-by: kosssi <simon@p4pillon.org>
2024-10-09 22:37:36 +02:00
d8b8ce9a77 feat: improve the fsv-sys README, and add a PROGESS.md for implementation tracking 2024-10-09 22:31:26 +02:00
9997ee43f8 feat: Gestion des versions multiples de FSV dans le wrapper exposant les fonctions de la librairie 2024-10-09 22:31:26 +02:00
4ab8a1de81 feat: handle multi-version bindings generation 2024-10-09 22:31:26 +02:00
d13f36c5e2 feat: Première implémentation de bindings pour FSV SESAM-Vitale
- Création de la crates/fsv-sys
- Ajout des headers de la FSV 1.40.14.13 dans crates/fsv-sys/vendor
- Génération des bindings depuis ces headers avec bindgen
- Implémentation d'une structure de loading de la librairie au runtime
- Implémentation d'une macro permettant de générer facilement la couche d'accès aux fonctions de la librairie
2024-10-09 22:31:26 +02:00
10 changed files with 207 additions and 68 deletions

View File

@ -2017,6 +2017,7 @@ name = "fsv"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"deku",
"env_logger",
"fsv-sys",
@ -2034,6 +2035,7 @@ name = "fsv-sys"
version = "0.1.0"
dependencies = [
"bindgen",
"chrono",
"libc",
"libloading 0.8.5",
"thiserror",

View File

@ -16,3 +16,6 @@ thiserror.workspace = true
[build-dependencies]
bindgen = "0.70.1"
[dev-dependencies]
chrono = "0.4.38"

View File

@ -14,13 +14,13 @@
| SSV_InitLIB2 |
| SSV_LireConfig |
| SSV_LireCartePS |
| SSV_LireDroitsVitale |
## SGD
| Fonctions implémentées |
|------------------------|
## SRT
| Fonctions implémentées |

View File

@ -13,13 +13,6 @@
Les détails de l'avancement de l'implémentation des bindings FSV sont donnés dans le fichier [PROGRESS.md](PROGRESS.md)
| Module | Progression |
|-------------|------------------------------------|
| [SSV](#ssv) | ![](https://geps.dev/progress/5) |
| [SGD](#sgd) | ![](https://geps.dev/progress/0) |
| [SRT](#srt) | ![](https://geps.dev/progress/0) |
| [STS](#sts) | ![](https://geps.dev/progress/0) |
## Utilisation
### Pré-requis
@ -39,5 +32,5 @@ Les détails de l'avancement de l'implémentation des bindings FSV sont donnés
### Pré-requis
- Pour la génération des bindings lors de la pahse de `build` à l'aide de `bindgen`, il est nécessaire d'avoir installé `clang` ([documentation](https://rust-lang.github.io/rust-bindgen/requirements.html)).
- Pour la génération des bindings lors de la phase de `build` à l'aide de `bindgen`, il est nécessaire d'avoir installé `clang` ([documentation](https://rust-lang.github.io/rust-bindgen/requirements.html)).

View File

@ -109,6 +109,15 @@ impl SSVLibrary<V1_40_14> {
pZDataOut: *mut *mut libc::c_void,
pTailleZone: *mut usize
});
ssv_function!(BINDINGS_V1_40_14::SSV_LireDroitsVitale, ssv_lire_droits_vitale, {
NomRessourcePS: *const i8,
NomRessourceLecteur: *const i8,
CodePorteurPS: *const i8,
DateConsultation: *const i8,
pZDataOut: *mut *mut libc::c_void,
pTailleZone: *mut usize
});
}
impl SSVLibrary<V1_40_13> {
@ -128,6 +137,15 @@ impl SSVLibrary<V1_40_13> {
pZDataOut: *mut *mut libc::c_void,
pTailleZone: *mut usize
});
ssv_function!(BINDINGS_V1_40_13::SSV_LireDroitsVitale, ssv_lire_droits_vitale, {
NomRessourcePS: *const i8,
NomRessourceLecteur: *const i8,
CodePorteurPS: *const i8,
DateConsultation: *const i8,
pZDataOut: *mut *mut libc::c_void,
pTailleZone: *mut usize
});
}
pub fn get_library_path(version: &SupportedFsvVersion) -> String {
@ -162,8 +180,14 @@ mod test {
use super::*;
#[test]
fn test_initlib2() {
mod common {
use super::*;
const CPS_READER_NAME: &str = "Gemalto PC Twin Reader (645D94C3) 00 00";
const CPS_PIN: &str = "1234";
const VITALE_READER_NAME: &str = "XXXXXX";
pub fn init_library() -> SSVLibrary<V1_40_13> {
let lib_path = &get_library_path(&SupportedFsvVersion::V1_40_13);
let ssv_library = SSVLibrary::<V1_40_13>::new(lib_path).expect("SSVLibrary::new failed");
@ -171,29 +195,24 @@ mod test {
CString::new(sesam_ini_path(&SupportedFsvVersion::V1_40_13)).expect("CString::new failed");
let result = unsafe { ssv_library.ssv_init_lib2(sesam_ini_str.as_ptr()) }.unwrap();
assert_eq!(result, 0);
ssv_library
}
#[test]
fn test_lire_config_and_carte_ps() {
let lib_path = &get_library_path(&SupportedFsvVersion::V1_40_13);
let ssv_library = SSVLibrary::<V1_40_13>::new(lib_path).expect("SSVLibrary::new failed");
let sesam_ini_str =
CString::new(sesam_ini_path(&SupportedFsvVersion::V1_40_13)).expect("CString::new failed");
let result = unsafe { ssv_library.ssv_init_lib2(sesam_ini_str.as_ptr()) }.unwrap();
assert_eq!(result, 0);
pub fn lire_config(ssv_library: &SSVLibrary<V1_40_13>) {
let mut buffer_ptr: *mut libc::c_void = ptr::null_mut();
let mut size: libc::size_t = 0;
let result = unsafe { ssv_library.ssv_lire_config(&mut buffer_ptr, &mut size) }.unwrap();
assert_eq!(result, 0);
unsafe { libc::free(buffer_ptr) };
}
pub fn lire_carte_ps(ssv_library: &SSVLibrary<V1_40_13>) {
let nom_ressource_ps =
CString::new("Gemalto PC Twin Reader (645D94C3) 00 00").expect("CString::new failed");
CString::new(CPS_READER_NAME).expect("CString::new failed");
let nom_ressource_lecteur =
CString::new("Gemalto PC Twin Reader (645D94C3) 00 00").expect("CString::new failed");
let code_porteur_ps = CString::new("1234").expect("CString::new failed");
CString::new("useless parameter").expect("CString::new failed");
let code_porteur_ps = CString::new(CPS_PIN).expect("CString::new failed");
let mut buffer_ptr: *mut libc::c_void = ptr::null_mut();
let mut size: libc::size_t = 0;
let result = unsafe {
@ -204,9 +223,60 @@ mod test {
&mut buffer_ptr,
&mut size,
)
}
.unwrap();
}.unwrap();
assert_eq!(result, 0);
unsafe { libc::free(buffer_ptr) };
}
pub fn lire_droits_vitale(ssv_library: &SSVLibrary<V1_40_13>) {
let nom_ressource_ps =
CString::new(CPS_READER_NAME).expect("CString::new failed");
let nom_ressource_lecteur =
CString::new(VITALE_READER_NAME).expect("CString::new failed");
let code_porteur_ps = CString::new(CPS_PIN).expect("CString::new failed");
// Today's date, in the format YYYYMMDD
let today = chrono::Local::now().format("%Y%m%d").to_string();
let date_consultation = CString::new(today).expect("CString::new failed");
let mut buffer_ptr: *mut libc::c_void = ptr::null_mut();
let mut size: libc::size_t = 0;
let result = unsafe {
ssv_library.ssv_lire_droits_vitale(
nom_ressource_ps.as_ptr(),
nom_ressource_lecteur.as_ptr(),
code_porteur_ps.as_ptr(),
date_consultation.as_ptr(),
&mut buffer_ptr,
&mut size,
)
}.unwrap();
assert_eq!(result, 0);
unsafe { libc::free(buffer_ptr) };
}
}
#[test]
fn test_initlib2() {
let _ssv_library = common::init_library();
}
#[test]
fn test_lire_config() {
let ssv_library = common::init_library();
common::lire_config(&ssv_library);
}
#[test]
#[ignore="This test requires a CPS card to be inserted in the reader"]
fn test_lire_carte_ps() {
let ssv_library = common::init_library();
common::lire_carte_ps(&ssv_library);
}
#[test]
#[ignore="This test requires a CPS card and a Vitale card to be inserted in the readers"]
fn test_lire_droits_vitale() {
let ssv_library = common::init_library();
common::lire_droits_vitale(&ssv_library);
}
}

View File

@ -18,6 +18,7 @@ utils = { path = "../utils" }
#[dev-dependencies]
log = "0.4.22"
env_logger = "0.11.5"
chrono = "0.4.38"
[dev-dependencies]
insta = "1.40.0"

View File

@ -3,6 +3,8 @@ use thiserror::Error;
#[derive(Error, Debug, Eq, PartialEq, FromPrimitive)]
#[repr(u16)]
/// Liste des codes d'erreur retournés par la librairie C SSV
/// Documentation: Manuel de programmation SSV - Annexe A (p. 215)
pub enum SSVErrorCodes {
#[error("La Carte du Professionnel de Santé est absente du lecteur.")]
CPSMissing = 0xF001,
@ -17,7 +19,7 @@ pub enum SSVErrorCodes {
/// - Sécurisation d'une série de lots en cours.
/// - Pour les fonctions TLA (sauf Identifier TLA) : Cette erreur survient lorsque le simulateur TLA est en mode 1.50.
/// - Lire Date Lecteur, Mettre à jour Date Lecteur, Lire Droits Vitale : Cette erreur peut survenir lorsque le Logiciel Lecteur ne connaît pas la fonction sollicitée, c'est-à-dire si la version du Logiciel Lecteur est antérieure à 2.00.
/// - Décharger Données Bénéficiaires : cette erreur peut survenir pour signaler que le
/// - Décharger Données Bénéficiaires : cette erreur peut survenir pour signaler que le format des données issues du lecteur est incompatible avec cette version de SSV.
#[error("F022: Erreur commune à plusieurs fonctions.")]
F022 = 0xF022,
#[error("Message du lecteur incohérent. Débrancher et rebrancher le lecteur.")]

View File

@ -37,6 +37,9 @@ pub struct SSV {
}
impl SSV {
const CPS_READER_NAME: &'static str = "Gemalto PC Twin Reader (645D94C3) 00 00";
const VITALE_READER_NAME: &'static str = "Gemalto PC Twin Reader (645D94C3) 00 00"; // TODO: Change this to the correct reader name
pub fn new(version: SupportedFsvVersion) -> Result<Self, Error> {
let library = match version {
SupportedFsvVersion::V1_40_13 => {
@ -77,18 +80,18 @@ impl SSV {
/// # Read the CPS card
/// Implement: SSV_LireCartePS
pub fn read_professional_card(&self, pin_code: &str) -> Result<Data, Error> {
let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00";
let pin_code = CString::new(pin_code).expect("CString::new failed");
let pcsc_reader_name = CString::new(pcsc_reader_name).expect("CString::new failed");
let cps_pcsc_reader_name = CString::new(SSV::CPS_READER_NAME).expect("CString::new failed");
let vitale_pcsc_reader_name = CString::new(SSV::VITALE_READER_NAME).expect("CString::new failed");
let mut out_buffer_ptr: *mut libc::c_void = ptr::null_mut();
let mut out_buffer_size: libc::size_t = 0;
let result = match &self.library {
SsvLibraryVersion::V1_40_13(library) => {
unsafe { library.ssv_lire_carte_ps(
pcsc_reader_name.as_ptr(),
pcsc_reader_name.as_ptr(),
cps_pcsc_reader_name.as_ptr(),
vitale_pcsc_reader_name.as_ptr(),
pin_code.as_ptr(),
&mut out_buffer_ptr,
&mut out_buffer_size)
@ -96,8 +99,8 @@ impl SSV {
},
SsvLibraryVersion::V1_40_14(library) => {
unsafe { library.ssv_lire_carte_ps(
pcsc_reader_name.as_ptr(),
pcsc_reader_name.as_ptr(),
cps_pcsc_reader_name.as_ptr(),
vitale_pcsc_reader_name.as_ptr(),
pin_code.as_ptr(),
&mut out_buffer_ptr,
&mut out_buffer_size)
@ -149,6 +152,58 @@ impl SSV {
unsafe { libc::free(out_buffer_ptr) };
Ok(config_blocks)
}
pub fn get_vitale_card_rights(&self, pin_code: &str) -> Result<Data, Error> {
let pin_code = CString::new(pin_code).expect("CString::new failed");
let cps_pcsc_reader_name = CString::new(SSV::CPS_READER_NAME).expect("CString::new failed");
let vitale_pcsc_reader_name = CString::new(SSV::VITALE_READER_NAME).expect("CString::new failed");
// Today's date, in the format YYYYMMDD
let today = chrono::Local::now().format("%Y%m%d").to_string();
let date_consultation = CString::new(today).expect("CString::new failed");
let mut out_buffer_ptr: *mut libc::c_void = ptr::null_mut();
let mut out_buffer_size: libc::size_t = 0;
let result = match &self.library {
SsvLibraryVersion::V1_40_13(library) => {
unsafe { library.ssv_lire_droits_vitale(
cps_pcsc_reader_name.as_ptr(),
vitale_pcsc_reader_name.as_ptr(),
pin_code.as_ptr(),
date_consultation.as_ptr(),
&mut out_buffer_ptr,
&mut out_buffer_size)
}?
},
SsvLibraryVersion::V1_40_14(library) => {
unsafe { library.ssv_lire_droits_vitale(
cps_pcsc_reader_name.as_ptr(),
vitale_pcsc_reader_name.as_ptr(),
pin_code.as_ptr(),
date_consultation.as_ptr(),
&mut out_buffer_ptr,
&mut out_buffer_size)
}?
},
};
if result != 0 {
// Free memory
unsafe { libc::free(out_buffer_ptr) };
let error = SSVErrorCodes::from(result);
return Err(Error::SSVError(error));
}
// Parse the buffer into a Data struct
let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, out_buffer_size) };
////////////////////////////
println!("{:?}", buffer);
////////////////////////////
let (_rest, vitale_blocks) = Data::from_bytes((buffer, 0)).unwrap();
// Free memory
unsafe { libc::free(out_buffer_ptr) };
Ok(vitale_blocks)
}
}
#[cfg(test)]
@ -185,7 +240,7 @@ mod tests {
#[test]
#[ignore="
WARNING: Read the card with PIN 1234 - Risk of blocking the card
WARNING: Read the card with PIN 1234 - Risk of blocking the card if it's not the right card
WARNING: This test will only work with GILBERT's PHARMOFFICE card (titulaire kit pharmacie)
"]
fn test_read_professional_card_good_pin() -> Result<()> {
@ -214,7 +269,7 @@ mod tests {
}
#[test]
#[ignore="WARNING: Read the card with PIN 0000 - Risk of blocking the card"]
#[ignore="WARNING: Read the card with PIN 0000 - Risk of blocking the card if it's not the right card"]
fn test_read_professional_card_bad_pin() -> Result<()> {
let lib = setup::init()?;
let pin_code = "0000";
@ -246,4 +301,17 @@ mod tests {
assert_eq!(header_content.ssv_version.0.revision, "20");
Ok(())
}
#[test]
#[ignore="
WARNING: Read the card with PIN 1234 - Risk of blocking the card if it's not the right card
WARNING: This test needs a CPS and a VITALE card available simultaneously
"]
fn test_get_vitale_card_rights() -> Result<()> {
let lib = setup::init()?;
let pin_code = "1234";
let _cps_blocks = lib.read_professional_card(pin_code)?;
let _vitale_blocks = lib.get_vitale_card_rights(pin_code)?;
Ok(())
}
}

View File

@ -17,6 +17,6 @@
if (user.avatar) {
return user.avatar;
}
return 'https://avatar.iran.liara.run/username?username=' + user.name;
return 'https://i.pravatar.cc/150?u=' + user.name;
};
</script>

View File

@ -29,15 +29,15 @@
const users: User[] = [
{ id: 1, name: 'John Doe', avatar: 'https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp' },
{ id: 2, name: 'Jane Doe', avatar: 'https://avatar.iran.liara.run/public' },
{ id: 3, name: 'Michel Moulin', avatar: '' },
{ id: 4, name: 'Jean Paris', avatar: '' },
{ id: 5, name: 'Marie Dupont', avatar: '' },
{ id: 6, name: 'Émilie Fournier', avatar: '' },
{ id: 7, name: 'Pierre Lefevre', avatar: '' },
{ id: 8, name: 'Sophie Lemoine', avatar: '' },
{ id: 9, name: 'Lucie Simon', avatar: '' },
{ id: 10, name: 'Kevin Boucher', avatar: '' },
{ id: 2, name: 'Jane Doe', avatar: 'https://i.pravatar.cc/150?u=JANEDOE728' },
{ id: 3, name: 'Michel Moulin' },
{ id: 4, name: 'Jean Paris' },
{ id: 5, name: 'Marie Dupont' },
{ id: 6, name: 'Émilie Fournier' },
{ id: 7, name: 'Pierre Lefevre' },
{ id: 8, name: 'Sophie Lemoine' },
{ id: 9, name: 'Lucie Simon' },
{ id: 10, name: 'Kevin Boucher' },
];
const loginModal = useTemplateRef('login_modal');