15 Commits

Author SHA1 Message Date
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
19 changed files with 37 additions and 1657 deletions

View File

@ -2020,7 +2020,6 @@ dependencies = [
"deku",
"env_logger",
"fsv-sys",
"insta",
"libc",
"log",
"num_enum",
@ -2844,18 +2843,6 @@ dependencies = [
"libc",
]
[[package]]
name = "insta"
version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"similar",
]
[[package]]
name = "instant"
version = "0.1.13"
@ -3150,12 +3137,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
@ -5403,12 +5384,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "similar"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
[[package]]
name = "siphasher"
version = "0.3.11"

View File

@ -91,20 +91,6 @@ cargo tauri dev --no-watch
> - le `backend` n'est rechargé que si des modifications sont détectées dans le dossier précisé par `-w crates/backend`
> - le rechargement du `desktop` est désactivé par l'option `--no-watch` ; en effet, le rechargement du `frontend` est déjà pris en charge par `bun` et ne nécessite pas de rechargement du `desktop`
## Tests
Les tests unitaires et d'intégration utilisants des cartes CPS ou Vitale réelles sont désactivés par défaut et doivent être
[explicitement activés](https://doc.rust-lang.org/book/ch11-02-running-tests.html#ignoring-some-tests-unless-specifically-requested) pour s'exécuter.
### Snapshots
Pour certains modules, des tests d'intégration s'appuient sur la crate [`insta`](https://insta.rs) pour générer et comparer des snapshots.
Pour faciliter la gestion de ces tests, il est recommandé d'installer `cargo-insta` :
```bash
cargo install cargo-insta
```
## Build
Pour packager le client `desktop`, il est nécessaire de faire appel à la CLI Tauri, qui se charge de gérer le build du `frontend` et son intégration au bundle :

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

@ -18,11 +18,3 @@ utils = { path = "../utils" }
#[dev-dependencies]
log = "0.4.22"
env_logger = "0.11.5"
[dev-dependencies]
insta = "1.40.0"
[profile.dev.package]
# Optimize insta (snapshot testing library) for faster compile times
insta.opt-level = 3
similar.opt-level = 3

View File

@ -1,6 +1,6 @@
use deku::deku_derive;
use super::{ groups, size_read, read_with_size };
use super::{ groups, size_read };
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
@ -19,7 +19,7 @@ pub struct Data {
/// The `DataBlock` are the main structures inside a `Data` struct
pub struct DataBlock {
pub header: BlockHeader,
#[deku(ctx = "header.group_id.0, header.data_size")]
#[deku(ctx = "header.group_id.0")]
pub content: DataGroup,
}
@ -32,7 +32,7 @@ pub struct BlockHeader {
pub group_id: GroupId,
#[deku(reader = "size_read(deku::reader)")]
pub data_size: u64,
pub data_size: u64, // This field is not really used, but we have to parse it to move the reader cursor
}
#[derive(Debug, PartialEq)]
@ -51,33 +51,17 @@ pub struct GroupId(
/// correct data structure, able to parse the data contained in
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
#[deku(ctx = "group_id: u16, data_size: u64", id = "group_id")]
#[deku(ctx = "group_id: u16", id = "group_id")]
#[allow(non_camel_case_types)]
pub enum DataGroup {
#[deku(id = 1)]
LireCartePS_Group1_Holder(
#[deku(reader = "read_with_size(deku::reader, data_size as usize)")]
groups::ssv_lire_carte_ps::group_1_holder::Holder),
#[deku(id = 2)]
LireCartePS_Group2_Situation(
#[deku(reader = "read_with_size(deku::reader, data_size as usize)")]
groups::ssv_lire_carte_ps::group_2_situation::Situation),
#[deku(id = 60)]
LireConfig_Group60_ConfigHeader(
#[deku(reader = "read_with_size(deku::reader, data_size as usize)")]
groups::ssv_lire_config::group_60_header_config::ConfigHeader),
LireConfig_Group60_ConfigHeader(groups::ssv_lire_config::group_60_header_config::ConfigHeader),
#[deku(id = 61)]
LireConfig_Group61_ReaderConfig(
#[deku(reader = "read_with_size(deku::reader, data_size as usize)")]
groups::ssv_lire_config::group_61_reader_config::ReaderConfig),
LireConfig_Group61_ReaderConfig(groups::ssv_lire_config::group_61_reader_config::ReaderConfig),
#[deku(id = 64)]
LireConfig_Group64_SVComponentsConfig(
#[deku(reader = "read_with_size(deku::reader, data_size as usize)")]
groups::ssv_lire_config::group_64_sv_config::SVComponentsConfig),
LireConfig_Group64_SVComponentsConfig(groups::ssv_lire_config::group_64_sv_config::SVComponentsConfig),
#[deku(id = 67)]
LireConfig_Group67_PCSCReaderConfig(
#[deku(reader = "read_with_size(deku::reader, data_size as usize)")]
groups::ssv_lire_config::group_67_pcsc_config::PCSCReaderConfig),
LireConfig_Group67_PCSCReaderConfig(groups::ssv_lire_config::group_67_pcsc_config::PCSCReaderConfig),
}
#[cfg(test)]
@ -235,4 +219,5 @@ mod tests {
let (_rest, val) = deku_testing::DekuTestWithGroupId::from_bytes((buffer, 0)).unwrap();
assert_eq!(val.group_id.0, 1910, "EX2: ID");
}
}

View File

@ -4,7 +4,6 @@ use deku::{deku_derive, DekuError};
use super::{ size_read, map_bytes_to_lossy_string };
pub mod ssv_lire_carte_ps;
pub mod ssv_lire_config;
/// # Convert a DataField to a specific type
@ -17,29 +16,10 @@ where
{
let text = String::from_utf8(data_field.data)
.map_err(|e| DekuError::Parse(e.to_string().into()))?;
text.parse::<T>()
T::from_str(&text)
.map_err(|e| DekuError::Parse(e.to_string().into()))
}
/// # Extract raw bytes from a DataField, through a Vec<u8>
/// This function is used as deku map function to extract raw bytes
/// from a DataField
fn map_raw_from_data_field<T>(data_field: DataField) -> Result<T, DekuError>
where
T: From<Vec<u8>>,
{
Ok(data_field.data.into())
}
/// # Extract an enum id from a string
/// Deku enums only supports numbers as id, and we usually extract strings
/// from data fields. This function is used as a context function to convert
/// a string, such as obtained with NumericString, to an enum id
pub fn extract_enum_id_from_str<T>(id_string: &str, default: T) -> T
where T: FromStr {
id_string.parse::<T>().unwrap_or(default)
}
// ------------------- DATA FIELD TYPES -------------------
/// # Data field structure
@ -57,24 +37,18 @@ struct DataField {
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
/// # Data field: Numeric string (x CN)
/// # Numeric string
/// TODO: check if all the characters are numeric
pub struct NumericString(
#[deku(map = "map_from_data_field")]
pub String
String
);
impl From<&str> for NumericString {
fn from(s: &str) -> Self {
NumericString(s.to_string())
}
}
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
/// # Data field: Alphanumeric string (x CA/CE)
pub struct AlphaNumericString(
#[deku(map = "map_from_data_field")]
pub String
String
);
impl From<&str> for AlphaNumericString {
fn from(s: &str) -> Self {
@ -82,22 +56,6 @@ impl From<&str> for AlphaNumericString {
}
}
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
/// # Data field: Raw bytes (x CB)
pub struct RawBytes(
#[deku(map = "map_raw_from_data_field")]
pub Vec<u8>
);
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
/// # Data field: Raw byte (1 CB)
pub struct RawByte(
#[deku(map = "|x: DataField| -> Result<u8, DekuError> { Ok(x.data[0]) }")]
pub u8
);
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
#[deku(endian = "big")]
@ -141,13 +99,4 @@ mod test {
assert_eq!(software_version.version, "07");
assert_eq!(software_version.revision, "20");
}
#[test]
fn test_map_from_data_field() {
let data_field = DataField {
data: vec![48, 55],
};
let id: u8 = map_from_data_field(data_field).unwrap();
assert_eq!(id, 7);
}
}

View File

@ -1,733 +0,0 @@
//! # Structures de parsing des données de la fonction SSV_LireCartePS
#![allow(clippy::explicit_auto_deref)] // False positive on ctx attributes when using extract_enum_id_from_str
use deku::deku_derive;
use crate::fsv_parsing::groups::{ extract_enum_id_from_str, AlphaNumericString, NumericString, RawByte };
/// # Titulaire
/// 1 occurence
pub mod group_1_holder {
use super::*;
/// Groupe 1 - Titulaire
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct Holder {
#[deku(temp)]
card_type_raw: NumericString, // Champ 1 : Type de carte PS (2 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*card_type_raw.0, 255)")]
pub card_type: CardPSType,
#[deku(temp)]
national_id_type_raw: NumericString, // Champ 2 : Type didentification nationale (1 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*national_id_type_raw.0, 255)")]
pub national_id_type: NationalIDType,
// TODO: handle national_id depending on national_id_type
pub national_id: AlphaNumericString, // /!\ CE and not CA - Champ 3 : N° didentification nationale (8-30 CE)
pub national_id_key: AlphaNumericString, // Champ 4 : Clé du N° didentification nationale (1 CN)
#[deku(temp)]
civility_code_raw: NumericString, // Champ 5 : Code civilité (2 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*civility_code_raw.0, 255)")]
pub civility_code: CivilityCode,
pub holder_lastname: AlphaNumericString, // /!\ CE and not CA - Champ 6 : Nom du PS (27 CE)
pub holder_firstname: AlphaNumericString, // /!\ CE and not CA - Champ 7 : Prénom du PS (27 CE)
#[deku(temp)]
category_card_size: u8, // Champ 8 : Catégorie Carte (1 CA)
pub category_card: CategoryCard,
}
// Fields
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
pub enum CardPSType {
#[deku(id = 0)]
CPS, // Carte de Professionnel de Santé (CPS)
#[deku(id = 1)]
CPF, // Carte de Professionnel de Santé en Formation (CPF)
#[deku(id = 2)]
CPE, // Carte de Personnel / Directeur⋅ice d'Établissement de Santé (CDE/CPE)
#[deku(id = 3)]
CPA, // Carte de Personnel / Directeur⋅ice Autorisé⋅e (CDA/CPA)
#[deku(id = 4)]
CPM, // Carte de Personne Morale
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
pub enum NationalIDType {
#[deku(id = 0)]
ADELI, // N° ADELI
#[deku(id = 1)]
ADELICabinet, // Id Cabinet ADELI + N° employé
#[deku(id = 2)]
DRASS, // N° DRASS
#[deku(id = 3)]
FINESS, // N° FINESS + N° employé
#[deku(id = 4)]
SIREN, // N° SIREN + N° employé
#[deku(id = 5)]
SIRET, // N° SIRET + N° employé
#[deku(id = 6)]
RPPSCabinet, // Id Cabinet RPPS + N° employé
#[deku(id = 8)]
RPPS, // N° RPPS
#[deku(id = 9)]
ADELIEtudiantMedecin, // N° Etudiant Médecin type ADELI sur 9 caractères (information transmise par lANS)
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
pub enum CivilityCode {
#[deku(id = 1)]
Adjudant,
#[deku(id = 2)]
Amiral,
#[deku(id = 3)]
Aspirant,
#[deku(id = 4)]
Aumonier,
#[deku(id = 5)]
Capitaine,
#[deku(id = 6)]
Cardinal,
#[deku(id = 7)]
Chanoine,
#[deku(id = 8)]
Colonel,
#[deku(id = 9)]
Commandant,
#[deku(id = 10)]
Commissaire,
#[deku(id = 11)]
Conseiller,
#[deku(id = 12)]
Directeur,
#[deku(id = 13)]
Docteur,
#[deku(id = 14)]
Douanier,
#[deku(id = 15)]
Epouxse,
#[deku(id = 16)]
Eveque,
#[deku(id = 17)]
General,
#[deku(id = 18)]
Gouverneur,
#[deku(id = 19)]
Ingenieur,
#[deku(id = 20)]
Inspecteur,
#[deku(id = 21)]
Lieutenant,
#[deku(id = 22)]
Madame,
#[deku(id = 23)]
Mademoiselle,
#[deku(id = 24)]
Maitre,
#[deku(id = 25)]
Marechal,
#[deku(id = 26)]
Medecin,
#[deku(id = 27)]
Mesdames,
#[deku(id = 28)]
Mesdemoiselles,
#[deku(id = 29)]
Messieurs,
#[deku(id = 30)]
Monseigneur,
#[deku(id = 31)]
Monsieur,
#[deku(id = 32)]
NotreDame,
#[deku(id = 33)]
Pasteur,
#[deku(id = 34)]
Prefet,
#[deku(id = 35)]
President,
#[deku(id = 36)]
Professeur,
#[deku(id = 37)]
Recteur,
#[deku(id = 38)]
Sergent,
#[deku(id = 39)]
SousPrefet,
#[deku(id = 40)]
Technicien,
#[deku(id = 41)]
Veuve,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(id_type="u8")]
pub enum CategoryCard {
#[deku(id = 74)] // T
Test,
#[deku(id = 72)] // R
Reelle,
#[deku(id_pat = "_")]
Unknown,
}
}
/// # Situation
/// 1-16 occurences
pub mod group_2_situation {
use super::*;
/// Groupe 2 - Situation du PS
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct Situation {
pub id: RawByte, // Champ 1 : N° logique de la situation de facturation du PS (1 CB)
#[deku(temp)]
practice_mode_raw: NumericString, // Champ 2 : Mode dexercice (2 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*practice_mode_raw.0, 255)")]
pub practice_mode: PracticeMode,
// #[deku(temp)]
pub practice_status_raw: NumericString, // Champ 3 : Statut dexercice (3 CN)
// #[deku(ctx = "extract_enum_id_from_str::<u8>(&*practice_status_raw.0, 255)")]
// pub practice_status: PracticeStatus,
#[deku(temp)]
activity_sector_raw: NumericString, // Champ 4 : Secteur dactivité (3 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*activity_sector_raw.0, 255)")]
pub activity_sector: ActivitySector,
#[deku(temp)]
structure_id_type_raw: NumericString, // Champ 5 : Type didentification structure (1 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*structure_id_type_raw.0, 255)")]
pub structure_id_type: StructureIDType,
pub structure_id: AlphaNumericString, // Champ 6 : N° didentification structure (14 CA)
pub structure_id_key: NumericString, // Champ 7 : Clé du n° didentification structure (1 CN)
pub structure_name: AlphaNumericString, // Champ 8 : Raison sociale structure (40 CE)
pub ps_billing_number: NumericString, // Champ 9 : N° didentification de facturation du PS (8 CN)
pub ps_billing_number_key: NumericString, // Champ 10 : Clé du n° didentification de facturation du PS (1 CN)
pub ps_replacement_number: AlphaNumericString, // Champ 11 : N° didentification du PS remplaçant (30 CA) -- TODO OPTIONNEL
pub ps_replacement_number_key: NumericString, // Champ 12 : Clé du n° didentification du PS remplaçant (1 CN) -- TODO OPTIONNEL
#[deku(temp)]
convention_code_raw: NumericString, // Champ 13 : Code conventionnel (1 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*convention_code_raw.0, 255)")]
pub convention_code: ConventionCode,
#[deku(temp)]
specialty_code_raw: NumericString, // Champ 14 : Code spécialité (2 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*specialty_code_raw.0, 255)")]
pub specialty_code: SpecialtyCode,
// #[deku(temp)]
pub rate_zone_code_raw: NumericString, // Champ 15 : Code zone tarifaire (2 CN)
// #[deku(ctx = "extract_enum_id_from_str::<u8>(&*rate_zone_code_raw.0, 255)")]
// pub rate_zone_code: RateZoneCode, // CF p53-55 - Attribution complexe, dépendant du practice_status, du specialty_code et du convention_code
#[deku(temp)]
ik_zone_code_raw: NumericString, // Champ 16 : Code zone IK - Indemnité kilométrique (2 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*ik_zone_code_raw.0, 255)")]
pub ik_zone_code: IkZoneCode,
#[deku(temp)]
approval_code_1_raw: NumericString, // Champ 17 : Code agrément 1 (1 CN)
#[deku(ctx = "extract_enum_id_from_str::<u8>(&*approval_code_1_raw.0, 255)")]
pub approval_code_1: ApprovalCode,
pub approval_code_2_raw: NumericString, // Champ 18 : Code agrément 2 (1 CN) - Non utilisé pour le moment
pub approval_code_3_raw: NumericString, // Champ 19 : Code agrément 3 (1 CN) - Non utilisé pour le moment
pub invoice_signature_permission: NumericString, // Champ 20 : Habilitation à signer une facture (1 CN)
pub lot_signature_permission: NumericString, // Champ 21 : Habilitation à signer un lot (1 CN)
}
// Fields
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Mode d'exercice
pub enum PracticeMode {
#[deku(id = 0)]
Liberal, // Libéral, exploitant, commerçant
#[deku(id = 1)]
Salarie,
#[deku(id = 4)]
Remplacant,
#[deku(id = 7)]
Benevole,
}
//
// #[deku_derive(DekuRead)]
// #[derive(Debug, PartialEq)]
// #[deku(ctx = "id: u8", id = "id")]
// /// Statut d'exercice
// pub enum PracticeStatus {
// // Cf. TAB-Statuts géré par lANS
// }
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Secteur d'activité
pub enum ActivitySector {
#[deku(id = 10)]
EtablissementPublicDeSante, // Etablissement Public de santé
#[deku(id = 11)]
HopitauxMilitaires, // Hôpitaux Militaires
#[deku(id = 16)]
EtablissementPrivePSPH, // Etablissement Privé PSPH
#[deku(id = 17)]
EtablissementPriveNonPSPH, // Etablissement Privé Non PSPH
#[deku(id = 25)]
DispensaireDeSoins, // Dispensaire de soins
#[deku(id = 26)]
AutresStructuresDeSoinsArmee, // Autres structures de soins relevant du Service de santé des armées
#[deku(id = 31)]
CabinetIndividuel, // Cabinet individuel
#[deku(id = 32)]
CabinetDeGroupe, // Cabinet de Groupe
#[deku(id = 33)]
ExerciceEnSociete, // Exercice en Société
#[deku(id = 34)]
SecteurPrivePHTempsPlein, // Secteur privé PH temps plein
#[deku(id = 35)]
TransportSanitaire, // Transport sanitaire
#[deku(id = 37)]
EntrepriseDInterim, // Entreprise d'intérim
#[deku(id = 41)]
EtablissementDeSoinsEtPrevention, // Etablissement de Soins et Prévention
#[deku(id = 42)]
PreventionEtSoinsEnEntreprise, // Prévention. Et Soins en Entreprise
#[deku(id = 43)]
SanteScolaireEtUniversitaire, // Santé scolaire & universitaire
#[deku(id = 44)]
RecrutementEtGestionRH, // Recrutement & gestion RH
#[deku(id = 45)]
PMIPlanificationFamiliale, // P.M.I. Planification familiale
#[deku(id = 51)]
EtablissementPourHandicapes, // Etablissement pour Handicapés
#[deku(id = 52)]
ComMarketingConsultingMedia, // Com/Marketing/Consulting/Media
#[deku(id = 53)]
EtablissementPersonnesAgees, // Etablissement Personnes Agées
#[deku(id = 54)]
EtablissementAideALaFamille, // Etablissement Aide à la famille
#[deku(id = 55)]
EtablissementDEnseignement, // Etablissement d'enseignement
#[deku(id = 56)]
EtablissementsDeProtectionDeLEnfance, // Etablissements de protection de l'enfance
#[deku(id = 57)]
EtablissementsDHebergementEtDeReadaptation, // Etablissements d'hébergement et de réadaptation
#[deku(id = 58)]
Recherche, // Recherche
#[deku(id = 61)]
AssurancePrivee, // Assurance Privée
#[deku(id = 62)]
OrganismeDeSecuriteSociale, // Organisme de Sécurité Sociale
#[deku(id = 65)]
MinistereEtServicesDeconcentres, // Ministère & Serv. Déconcentrés
#[deku(id = 66)]
CollectivitesTerritoriales, // Collectivités Territoriales
#[deku(id = 68)]
AssoEtOrgaHumanitaire, // Asso et orga humanitaire
#[deku(id = 71)]
LABM, // LABM
#[deku(id = 75)]
AutreEtablissementSanitaire, // Autre établissement Sanitaire
#[deku(id = 81)]
ProdEtComGrosBienMed, // Prod. & Com. Gros Bien Med.
#[deku(id = 85)]
CommDetailDeBiensMedicaux, // Comm. Détail de biens médicaux
#[deku(id = 86)]
PharmacieDOfficine, // Pharmacie d'officine
#[deku(id = 87)]
CentreDeDialyse, // Centre de dialyse
#[deku(id = 88)]
ParaPharmacie, // Para-pharmacie
#[deku(id = 91)]
AutreSecteurDActivite, // Autre secteur d'activité
#[deku(id = 92)]
SecteurNonDefini, // Secteur non défini
#[deku(id = 93)]
CentreAntiCancer, // Centre anti-cancer
#[deku(id = 94)]
CentreDeTransfusionSanguine, // Centre de transfusion sanguine
#[deku(id = 95)]
ChaineDuMedicament, // Répart. Distrib. Fab. Exploit. Import Médicaments
#[deku(id = 96)]
IncendiesEtSecours, // Incendies et secours
#[deku(id = 97)]
EntreprisesIndustriellesNonPharma, // Entreprises industrielles et tertiaires hors industries pharmaceutiques
#[deku(id = 98)]
EntiteDUnTOM, // Entité d'un TOM
#[deku(id = 99)]
ChaineDuDispositifMedical, // Fab. Exploit. Import. Médicaments et Dispositifs Médicaux
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Type d'identification structure
pub enum StructureIDType {
#[deku(id = 0)]
ADELICabinet, // Id Cabinet ADELI
#[deku(id = 1)]
FINESS, // N° FINESS
#[deku(id = 2)]
SIREN, // N° SIREN
#[deku(id = 3)]
SIRET, // N° SIRET
#[deku(id = 4)]
RPPSCabinet, // Id Cabinet RPPS
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Code conventionnel
/// Dictionnaire des données FSV, p37
pub enum ConventionCode {
#[deku(id = 0)]
NonConventionne,
#[deku(id = 1)]
Conventionne,
#[deku(id = 2)]
ConventionneAvecDepassement,
#[deku(id = 3)]
ConventionneAvecHonorairesLibres,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Code spécialité
/// Dictionnaire des données FSV, p43
pub enum SpecialtyCode {
#[deku(id = 1)]
MedecineGenerale, // Médecine générale
#[deku(id = 2)]
AnesthesieReanimation, // Anesthésie-Réanimation
#[deku(id = 3)]
Cardiologie, // Cardiologie
#[deku(id = 4)]
ChirurgieGenerale, // Chirurgie Générale
#[deku(id = 5)]
DermatoVenerologie, // Dermatologie et Vénérologie
#[deku(id = 6)]
Radiologie, // Radiologie
#[deku(id = 7)]
GynecologieObstetrique, // Gynécologie obstétrique
#[deku(id = 8)]
GastroEnterologieHepatologie, // Gastro-Entérologie et Hépatologie
#[deku(id = 9)]
MedecineInterne, // Médecine interne
#[deku(id = 10)]
NeuroChirurgie, // Neuro-Chirurgie
#[deku(id = 11)]
OtoRhinoLaryngologie, // Oto-Rhino-Laryngologie
#[deku(id = 12)]
Pediatrie, // Pédiatrie
#[deku(id = 13)]
Pneumologie, // Pneumologie
#[deku(id = 14)]
Rhumatologie, // Rhumatologie
#[deku(id = 15)]
Ophtalmologie, // Ophtalmologie
#[deku(id = 16)]
ChirurgieUrologique, // Chirurgie urologique
#[deku(id = 17)]
NeuroPsychiatrie, // Neuro-Psychiatrie
#[deku(id = 18)]
Stomatologie, // Stomatologie
#[deku(id = 19)]
ChirurgienDentiste, // Chirurgien dentiste
#[deku(id = 20)]
ReanimationMedicale, // Réanimation médicale
#[deku(id = 21)]
SageFemme, // Sage-femme
#[deku(id = 22)]
SpécialisteEnMGDiplome, // Spécialiste en médecine générale avec diplôme
#[deku(id = 23)]
SpécialisteEnMGReconnu, // Spécialiste en médecine générale reconnu par lOrdre
#[deku(id = 24)]
Infirmier, // Infirmier
#[deku(id = 25)]
Psychologue, // Psychologue
#[deku(id = 26)]
MasseurKinesitherapeute, // Masseur Kinésithérapeute
#[deku(id = 27)]
PedicurePodologue, // Pédicure Podologue
#[deku(id = 28)]
Orthophoniste, // Orthophoniste
#[deku(id = 29)]
Orthoptiste, // Orthoptiste
#[deku(id = 30)]
LaboAnalysesMedicales, // Laboratoire d'analyses médicales
#[deku(id = 31)]
ReeducationReadaptationFonctionnelle, // Rééducation Réadaptation fonctionnelle
#[deku(id = 32)]
Neurologie, // Neurologie
#[deku(id = 33)]
Psychiatrie, // Psychiatrie
#[deku(id = 34)]
Geriatrie, // Gériatrie
#[deku(id = 35)]
Nephrologie, // Néphrologie
#[deku(id = 36)]
ChirurgieDentaireSpecialiteODF, // Chirurgie Dentaire spécialité O.D.F
#[deku(id = 37)]
AnatomoCytoPathologie, // Anatomo-Cyto-Pathologie
#[deku(id = 38)]
MedecinBiologiste, // Médecin biologiste
#[deku(id = 39)]
LaboPolyvalent, // Laboratoire polyvalent
#[deku(id = 40)]
LaboAnatomoCytoPathologique, // Laboratoire danatomo-cyto-pathologique
#[deku(id = 41)]
ChirurgieOrthopediqueTraumatologie, // Chirurgie Orthopédique et Traumatologie
#[deku(id = 42)]
EndocrinologieMetabolisme, // Endocrinologie et Métabolisme
#[deku(id = 43)]
ChirurgieInfantile, // Chirurgie infantile
#[deku(id = 44)]
ChirurgieMaxilloFaciale, // Chirurgie maxillo-faciale
#[deku(id = 45)]
ChirurgieMaxilloFacialeStomatologie, // Chirurgie maxillo-faciale et stomatologie
#[deku(id = 46)]
ChirurgiePlastiqueReconstructriceEsthetique, // Chirurgie plastique reconstructrice et esthétique
#[deku(id = 47)]
ChirurgieThoraciqueCardioVasculaire, // Chirurgie thoracique et cardio-vasculaire
#[deku(id = 48)]
ChirurgieVasculaire, // Chirurgie vasculaire
#[deku(id = 49)]
ChirurgieVisceraleDigestive, // Chirurgie viscérale et digestive
#[deku(id = 50)]
PharmacieDOfficine, // Pharmacie dofficine
#[deku(id = 51)]
PharmacieMutualiste, // Pharmacie Mutualiste
#[deku(id = 53)]
ChirurgienDentisteSpecialiteCO, // Chirurgien dentiste spécialité C.O.
#[deku(id = 54)]
ChirurgienDentisteSpecialiteMBD, // Chirurgien dentiste spécialité M.B.D.
#[deku(id = 60)]
PrestataireDeTypeSociete, // Prestataire de type société
#[deku(id = 61)]
PrestataireArtisan, // Prestataire artisan
#[deku(id = 62)]
PrestataireDeTypeAssociation, // Prestataire de type association
#[deku(id = 63)]
Orthesiste, // Orthésiste
#[deku(id = 64)]
Opticien, // Opticien
#[deku(id = 65)]
Audioprothesiste, // Audioprothésiste
#[deku(id = 66)]
EpithesisteOculariste, // Épithésiste Oculariste
#[deku(id = 67)]
PodoOrthesiste, // Podo-orthésiste
#[deku(id = 68)]
Orthoprothesiste, // Orthoprothésiste
#[deku(id = 69)]
ChirurgieOrale, // Chirurgie orale
#[deku(id = 70)]
GynecologieMedicale, // Gynécologie médicale
#[deku(id = 71)]
Hematologie, // Hématologie
#[deku(id = 72)]
MedecineNucleaire, // Médecine nucléaire
#[deku(id = 73)]
OncologieMedicale, // Oncologie médicale
#[deku(id = 74)]
OncologieRadiotherapique, // Oncologie radiothérapique
#[deku(id = 75)]
PsychiatrieEnfantAdolescent, // Psychiatrie de lenfant et de ladolescent
#[deku(id = 76)]
Radiotherapie, // Radiothérapie
#[deku(id = 77)]
Obstetrique, // Obstétrique
#[deku(id = 78)]
GenetiqueMedicale, // Génétique médicale
#[deku(id = 79)]
ObstetriqueGynecologieMedicale, // Obstétrique et Gynécologie médicale
#[deku(id = 80)]
SantePubliqueMedecineSociale, // Santé publique et médecine sociale
#[deku(id = 81)]
MaladiesInfectieusesTropicales, // Médecine des Maladies infectieuses et tropicales
#[deku(id = 82)]
MedecineLegaleExpertisesMedicales, // Médecine légale et expertises médicales
#[deku(id = 83)]
MedecineDurgence, // Médecine durgence
#[deku(id = 84)]
MedecineVasculaire, // Médecine vasculaire
#[deku(id = 85)]
Allergologie, // Allergologie
#[deku(id = 86)]
IPA, // Infirmier exerçant en Pratiques Avancées (IPA)
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Code zone IK - Indemnité kilométrique
/// Dictionnaire des données FSV, p44
pub enum IkZoneCode {
#[deku(id = 0)]
PasDIndemniteKilometrique, // Pas d'indemnité kilométrique
#[deku(id = 1)]
IndemniteKilometriquePlaine, // Indemnité kilométrique plaine
#[deku(id = 2)]
IndemniteKilometriqueMontagne, // Indemnité kilométrique montagne
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "id: u8", id = "id")]
/// Code agrément
/// Dictionnaire des données FSV, p44
pub enum ApprovalCode {
#[deku(id = 0)]
PasAgrementRadio, // Pas d'agrément radio
#[deku(id = 1)]
AgrementDOuDDASS, // Agrément D ou DDASS
#[deku(id = 2)]
AgrementABCEF, // Agrément A, B, C, E, F
#[deku(id = 3)]
AgrementGHJ, // Agrément G, H, J
#[deku(id = 4)]
AgrementK, // Agrément K
#[deku(id = 5)]
AgrementL, // Agrément L
#[deku(id = 6)]
AgrementM, // Agrément M
}
}
#[cfg(test)]
mod tests {
use deku::DekuContainerRead as _;
use group_1_holder::{CardPSType, CategoryCard, CivilityCode, NationalIDType};
use group_2_situation::{ActivitySector, ApprovalCode, ConventionCode, IkZoneCode, PracticeMode, SpecialtyCode, StructureIDType};
use crate::fsv_parsing::blocks::BlockHeader;
use super::*;
mod data {
pub const BUFFER: &[u8] = &[
0, 1, // Block ID
53, // Block size
1, // Type de carte PS, 2 CN
48, // 0
1, // Type d'identification nationale, 1 CN
56, // 8
11, // N° d'identification nationale, 8-30 CE
57, 57, 55, 48, 48, 53, 57, 51, 54, 56, 54,
1, // Clé du N° d'identification nationale, 1 CN
54, // 6
2, // Code civilité, 2 CN
51, 49, // 31
23, // Nom du PS, 27 CE
80, 72, 65, 82, 77, 79, 70, 70, 73, 67,
69, 32, 82, 80, 80, 83, 48, 48, 53, 57,
51, 54, 56,
7, // Prénom du PS, 27 CE
71, 73, 76, 66, 69, 82, 84,
// ??? Missing ??? Catégorie Carte, 1 CA
0, 2, // Block ID
93, // Block size
1, // N° logique de la situation de facturation du PS, 1 CB
1,
1, // Mode d'exercice, 2 CN
48,
1, // Statut d'exercice, 3 CN
49,
2, // Secteur d'activité, 3 CN
56, 54,
1, // Type d'identification structure, 1 CN
49,
9, // N° d'identification structure, 14 CA
48, 66, 48, 50, 52, 54, 50, 56, 54, // 0B0246286
1, // Clé du N° d'identification structure, 1 CN
54,
34, // Raison sociale structure, 40 CE
80, 72, 65, 82, 77, 65, 67, 73, 69, 32,
68, 69, 32, 76, 65, 32, 71, 65, 82, 69,
32, 82, 79, 85, 84, 73, 69, 82, 69, 50,
52, 54, 50, 56,
8, // N° d'identification de facturation du PS, 8 CN
48, 48, 50, 48, 57, 51, 54, 56, // 00209368
1, // Clé du N° d'identification de facturation du PS, 1 CN
48,
0, // N° d'identification du PS remplaçant, 30 CA
1, // Clé du N° d'identification du PS remplaçant, 1 CN
48,
1, // Code conventionnel, 1 CN
49,
2, // Code spécialité, 2 CN
53, 48,
2, // Code zone tarifaire, 2 CN
49, 48,
2, // Code zone IK
48, 48,
1, // Code agrément 1, 1 CN
48,
1, // Code agrément 2, 1 CN
48,
1, // Code agrément 3, 1 CN
48,
1, // Habilitation à signer une Facture // 1 CN
49,
1, // Habilitation à signer un lot // 1 CN
49,
];
}
#[test]
fn test_lire_carte_ps_first_header() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let ((_rest, _offset), block_header) = BlockHeader::from_bytes((data::BUFFER, 0)).unwrap();
assert_eq!(block_header.group_id.0, 1, "Header ID");
assert_eq!(block_header.data_size, 53, "Header Size");
}
#[test]
fn test_group_1_holder() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let offset = 3*8;
let (_rest, holder) = group_1_holder::Holder::from_bytes((data::BUFFER, offset)).unwrap();
assert_eq!(holder.card_type, CardPSType::CPS, "Card type");
assert_eq!(holder.national_id_type, NationalIDType::RPPS, "National ID type");
assert_eq!(holder.national_id.0, "99700593686", "National Id");
assert_eq!(holder.national_id_key.0, "6", "National ID Key");
assert_eq!(holder.civility_code, CivilityCode::Monsieur, "Civility Code");
assert_eq!(holder.holder_lastname.0, "PHARMOFFICE RPPS0059368", "Holder Lastname");
assert_eq!(holder.holder_firstname.0, "GILBERT", "Holder Firstname");
assert_eq!(holder.category_card, CategoryCard::Unknown, "Category card");
}
#[test]
fn test_group_2_situation() {
// env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging
let offset = 3*8 + 53*8 + 3*8;
let (_rest, situation) = group_2_situation::Situation::from_bytes((data::BUFFER, offset)).unwrap();
assert_eq!(situation.id.0, 1, "ID");
assert_eq!(situation.practice_mode, PracticeMode::Liberal, "Practice mode");
assert_eq!(situation.practice_status_raw.0, "1", "Practice status raw");
assert_eq!(situation.activity_sector, ActivitySector::PharmacieDOfficine, "Activity sector");
assert_eq!(situation.structure_id_type, StructureIDType::FINESS, "Structure ID type");
assert_eq!(situation.structure_id.0, "0B0246286", "Structure ID");
assert_eq!(situation.structure_id_key.0, "6", "Structure ID key");
assert_eq!(situation.structure_name.0, "PHARMACIE DE LA GARE ROUTIERE24628", "Structure name");
assert_eq!(situation.ps_billing_number.0, "00209368", "PS billing number");
assert_eq!(situation.ps_billing_number_key.0, "0", "PS billing number key");
assert_eq!(situation.ps_replacement_number.0, "", "PS replacement number");
assert_eq!(situation.ps_replacement_number_key.0, "0", "PS replacement number key");
assert_eq!(situation.convention_code, ConventionCode::Conventionne, "Convention code");
assert_eq!(situation.specialty_code, SpecialtyCode::PharmacieDOfficine, "Specialty code");
assert_eq!(situation.rate_zone_code_raw.0, "10", "Rate zone code raw");
assert_eq!(situation.ik_zone_code, IkZoneCode::PasDIndemniteKilometrique, "IK zone code");
assert_eq!(situation.approval_code_1, ApprovalCode::PasAgrementRadio, "Approval code 1");
assert_eq!(situation.approval_code_2_raw.0, "0", "Approval code 2 raw");
assert_eq!(situation.approval_code_3_raw.0, "0", "Approval code 3 raw");
assert_eq!(situation.invoice_signature_permission.0, "1", "Invoice signature permission");
assert_eq!(situation.lot_signature_permission.0, "1", "Lot signature permission");
}
}

View File

@ -209,7 +209,7 @@ mod tests {
let ((_rest, _offset), block_header) = BlockHeader::from_bytes((buffer, offset)).unwrap();
assert_eq!(block_header.group_id.0, 60, "Header ID");
assert_eq!(block_header.data_size, 15, "Header Size");
// assert_eq!(block_header.data_size, 15, "Header Size");
}
#[test]
@ -289,7 +289,6 @@ mod tests {
assert_eq!(content.name.0.0, "Gemalto PC Twin Reader (645D94C3) 00 00", "Reader Name");
assert_eq!(content.card_type.0.0, "2", "Card Type");
},
_ => panic!("Unexpected data block type"),
}
}
}

View File

@ -33,26 +33,6 @@ fn size_read<R: std::io::Read + std::io::Seek>(reader: &mut Reader<R>) -> Result
Ok(size)
}
/// Deku Reader taking an expected size into account
/// This function limit the reading to the size given in input
fn read_with_size<T, R: std::io::Read + std::io::Seek>(
reader: &mut Reader<R>,
size: usize
) -> Result<T, DekuError>
where T: for<'a> DekuContainerRead<'a>
{
let max_size = core::mem::size_of::<T>();
let mut buf = vec![0; max_size];
let buf: &mut [u8] = &mut buf;
let ret = reader.read_bytes(size, buf)?;
let (_rest, block) = match ret {
ReaderRet::Bytes => {
T::from_bytes((buf, 0))?
},
_ => return Err(DekuError::Parse("Unexpected result reading size bytes: got bits".into())),
};
Ok(block)
}
/// # Map bytes to a lossy string
/// This function is used to map bytes to a string, ignoring invalid UTF-8 characters
@ -63,70 +43,3 @@ fn map_bytes_to_lossy_string(data: &[u8]) -> Result<String, DekuError> {
let version: String = String::from_utf8_lossy(data).to_string();
Ok(version)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_with_size_reader() {
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
struct Data {
#[deku(read_all)]
pub blocks: Vec<Block>,
}
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
struct Block {
pub size: u8,
pub id: u8,
#[deku(ctx = "*id,*size-1")] // size-1 to remove the ID size
pub data: BlockType,
}
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
#[deku(ctx = "id: u8, size: u8", id = "id")]
enum BlockType {
#[deku(id = 1)]
Block1(
#[deku(reader = "read_with_size(deku::reader, size as usize)")]
Block1
),
#[deku(id = 2)]
Block2(
#[deku(reader = "read_with_size(deku::reader, size as usize)")]
Block2
),
}
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
struct Block1 {
pub field1_size: u8,
pub field1: u16,
pub field2_size: u8,
pub field2: u64,
}
#[derive(Debug, PartialEq)]
#[deku_derive(DekuRead)]
struct Block2;
let buffer = &[
// 1st Block, type 1
4, // Size: 4
1, // ID: 1
2, 0x12, 0x34, // Field 1, size 2
// No Field 2
// 2nd Block, type 1
6, // Size: Y
1, // ID: 2
1, 0x56, // Field 1, size 1 (casted into u16)
2, 0x78, 0x9A // Field 2, size 2
];
let (_rest, val) = Data::from_bytes((buffer, 0)).unwrap();
assert_eq!(val.blocks.len(), 2);
assert_eq!(val.blocks[0].size, 4);
assert_eq!(val.blocks[1].size, 6);
}
}

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

@ -14,7 +14,7 @@ use fsv_sys::{
mod errors_ssv;
pub use errors_ssv::SSVErrorCodes;
use errors_ssv::SSVErrorCodes;
use crate::fsv_parsing::prelude::*;
#[derive(Error, Debug)]
@ -76,7 +76,7 @@ impl SSV {
/// # Read the CPS card
/// Implement: SSV_LireCartePS
pub fn read_professional_card(&self, pin_code: &str) -> Result<Data, Error> {
pub fn read_professional_card(&self, pin_code: &str) -> Result<(), Error> {
let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00";
let pin_code = CString::new(pin_code).expect("CString::new failed");
@ -111,13 +111,12 @@ impl SSV {
let error = SSVErrorCodes::from(result);
return Err(Error::SSVError(error));
}
// Parse the buffer into a Data struct
let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, out_buffer_size) };
let (_rest, cps_blocks) = Data::from_bytes((buffer, 0)).unwrap();
// Print 10 bytes of the buffer
let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, 10) };
println!("{:?}", buffer);
// Free memory
unsafe { libc::free(out_buffer_ptr) };
Ok(cps_blocks)
Ok(())
}
/// # Get the configuration of the SSV library
@ -158,7 +157,7 @@ mod tests {
use utils::config::load_config;
use anyhow::{bail, Result};
use crate::fsv_parsing::{blocks::DataGroup, groups::ssv_lire_carte_ps::{group_1_holder::CardPSType, group_2_situation::{PracticeMode, SpecialtyCode}}};
use crate::fsv_parsing::blocks::DataGroup;
use super::*;
@ -184,32 +183,11 @@ mod tests {
}
#[test]
#[ignore="
WARNING: Read the card with PIN 1234 - Risk of blocking the card
WARNING: This test will only work with GILBERT's PHARMOFFICE card (titulaire kit pharmacie)
"]
#[ignore="WARNING: Read the card with PIN 1234 - Risk of blocking the card"]
fn test_read_professional_card_good_pin() -> Result<()> {
let lib = setup::init()?;
let pin_code = "1234";
let cps_blocks = lib.read_professional_card(pin_code)?;
// Check the first group is the holder group
let holder_group = cps_blocks.blocks.first().unwrap();
assert_eq!(holder_group.header.group_id.0, 1);
let holder_content = match &holder_group.content {
DataGroup::LireCartePS_Group1_Holder(content) => { content },
_ => bail!("Wrong group type"),
};
assert_eq!(holder_content.card_type, CardPSType::CPS, "Card type");
assert_eq!(holder_content.holder_firstname.0, "GILBERT", "Holder firstname");
// Check the second group is a situation group
let situation_group = cps_blocks.blocks.get(1).unwrap();
assert_eq!(situation_group.header.group_id.0, 2);
let situation_content = match &situation_group.content {
DataGroup::LireCartePS_Group2_Situation(content) => { content },
_ => bail!("Wrong group type"),
};
assert_eq!(situation_content.practice_mode, PracticeMode::Liberal, "Practice mode");
assert_eq!(situation_content.specialty_code, SpecialtyCode::PharmacieDOfficine, "Specialty code");
lib.read_professional_card(pin_code)?;
Ok(())
}

View File

@ -1,97 +0,0 @@
---
source: crates/fsv/tests/test_ssv.rs
expression: cps_blocks
---
Data {
blocks: [
DataBlock {
header: BlockHeader {
group_id: GroupId(
1,
),
data_size: 36,
},
content: LireCartePS_Group1_Holder(
Holder {
card_type: CPS,
national_id_type: RPPS,
national_id: AlphaNumericString(
"99700619010",
),
national_id_key: AlphaNumericString(
"0",
),
civility_code: Monsieur,
holder_lastname: AlphaNumericString(
"DOC0061901",
),
holder_firstname: AlphaNumericString(
"KIT",
),
category_card: Unknown,
},
),
},
DataBlock {
header: BlockHeader {
group_id: GroupId(
2,
),
data_size: 84,
},
content: LireCartePS_Group2_Situation(
Situation {
id: RawByte(
2,
),
practice_mode: Liberal,
practice_status_raw: NumericString(
"1",
),
activity_sector: CabinetIndividuel,
structure_id_type: RPPSCabinet,
structure_id: AlphaNumericString(
"99700619010009",
),
structure_id_key: NumericString(
"0",
),
structure_name: AlphaNumericString(
"CABINET M DOC0061901",
),
ps_billing_number: NumericString(
"00102901",
),
ps_billing_number_key: NumericString(
"6",
),
ps_replacement_number: AlphaNumericString(
"",
),
ps_replacement_number_key: NumericString(
"0",
),
convention_code: Conventionne,
specialty_code: MedecineGenerale,
rate_zone_code_raw: NumericString(
"24",
),
ik_zone_code: IndemniteKilometriquePlaine,
approval_code_1: PasAgrementRadio,
approval_code_2_raw: NumericString(
"0",
),
approval_code_3_raw: NumericString(
"0",
),
invoice_signature_permission: NumericString(
"1",
),
lot_signature_permission: NumericString(
"1",
),
},
),
},
],
}

View File

@ -1,97 +0,0 @@
---
source: crates/fsv/tests/test_ssv.rs
expression: cps_blocks
---
Data {
blocks: [
DataBlock {
header: BlockHeader {
group_id: GroupId(
1,
),
data_size: 55,
},
content: LireCartePS_Group1_Holder(
Holder {
card_type: CPS,
national_id_type: RPPS,
national_id: AlphaNumericString(
"99700593694",
),
national_id_key: AlphaNumericString(
"4",
),
civility_code: Madame,
holder_lastname: AlphaNumericString(
"ADJOINTPHARM RPPS0059369",
),
holder_firstname: AlphaNumericString(
"PATRICIA",
),
category_card: Unknown,
},
),
},
DataBlock {
header: BlockHeader {
group_id: GroupId(
2,
),
data_size: 93,
},
content: LireCartePS_Group2_Situation(
Situation {
id: RawByte(
1,
),
practice_mode: Salarie,
practice_status_raw: NumericString(
"2",
),
activity_sector: PharmacieDOfficine,
structure_id_type: FINESS,
structure_id: AlphaNumericString(
"0B0246286",
),
structure_id_key: NumericString(
"6",
),
structure_name: AlphaNumericString(
"PHARMACIE DE LA GARE ROUTIERE24628",
),
ps_billing_number: NumericString(
"00209368",
),
ps_billing_number_key: NumericString(
"0",
),
ps_replacement_number: AlphaNumericString(
"",
),
ps_replacement_number_key: NumericString(
"0",
),
convention_code: Conventionne,
specialty_code: PharmacieDOfficine,
rate_zone_code_raw: NumericString(
"10",
),
ik_zone_code: PasDIndemniteKilometrique,
approval_code_1: PasAgrementRadio,
approval_code_2_raw: NumericString(
"0",
),
approval_code_3_raw: NumericString(
"0",
),
invoice_signature_permission: NumericString(
"1",
),
lot_signature_permission: NumericString(
"1",
),
},
),
},
],
}

View File

@ -1,97 +0,0 @@
---
source: crates/fsv/tests/test_ssv.rs
expression: cps_blocks
---
Data {
blocks: [
DataBlock {
header: BlockHeader {
group_id: GroupId(
1,
),
data_size: 49,
},
content: LireCartePS_Group1_Holder(
Holder {
card_type: CPE,
national_id_type: FINESS,
national_id: AlphaNumericString(
"0B0246286/CPET00001",
),
national_id_key: AlphaNumericString(
"6",
),
civility_code: Monsieur,
holder_lastname: AlphaNumericString(
"EMPLOYE246280001",
),
holder_firstname: AlphaNumericString(
"AA",
),
category_card: Unknown,
},
),
},
DataBlock {
header: BlockHeader {
group_id: GroupId(
2,
),
data_size: 93,
},
content: LireCartePS_Group2_Situation(
Situation {
id: RawByte(
1,
),
practice_mode: Salarie,
practice_status_raw: NumericString(
"0",
),
activity_sector: PharmacieDOfficine,
structure_id_type: FINESS,
structure_id: AlphaNumericString(
"0B0246286",
),
structure_id_key: NumericString(
"6",
),
structure_name: AlphaNumericString(
"PHARMACIE DE LA GARE ROUTIERE24628",
),
ps_billing_number: NumericString(
"00209368",
),
ps_billing_number_key: NumericString(
"0",
),
ps_replacement_number: AlphaNumericString(
"",
),
ps_replacement_number_key: NumericString(
"0",
),
convention_code: Conventionne,
specialty_code: PharmacieDOfficine,
rate_zone_code_raw: NumericString(
"10",
),
ik_zone_code: PasDIndemniteKilometrique,
approval_code_1: PasAgrementRadio,
approval_code_2_raw: NumericString(
"0",
),
approval_code_3_raw: NumericString(
"0",
),
invoice_signature_permission: NumericString(
"1",
),
lot_signature_permission: NumericString(
"0",
),
},
),
},
],
}

View File

@ -1,97 +0,0 @@
---
source: crates/fsv/tests/test_ssv.rs
expression: cps_blocks
---
Data {
blocks: [
DataBlock {
header: BlockHeader {
group_id: GroupId(
1,
),
data_size: 50,
},
content: LireCartePS_Group1_Holder(
Holder {
card_type: CPS,
national_id_type: RPPS,
national_id: AlphaNumericString(
"99700520499",
),
national_id_key: AlphaNumericString(
"9",
),
civility_code: Madame,
holder_lastname: AlphaNumericString(
"INFIRMIERE RPPS0052049",
),
holder_firstname: AlphaNumericString(
"ALINE",
),
category_card: Unknown,
},
),
},
DataBlock {
header: BlockHeader {
group_id: GroupId(
2,
),
data_size: 93,
},
content: LireCartePS_Group2_Situation(
Situation {
id: RawByte(
1,
),
practice_mode: Liberal,
practice_status_raw: NumericString(
"1",
),
activity_sector: CabinetIndividuel,
structure_id_type: RPPSCabinet,
structure_id: AlphaNumericString(
"99700520499002",
),
structure_id_key: NumericString(
"0",
),
structure_name: AlphaNumericString(
"CABINET MME INFIRMIERE0052049",
),
ps_billing_number: NumericString(
"00602049",
),
ps_billing_number_key: NumericString(
"9",
),
ps_replacement_number: AlphaNumericString(
"",
),
ps_replacement_number_key: NumericString(
"0",
),
convention_code: Conventionne,
specialty_code: Infirmier,
rate_zone_code_raw: NumericString(
"20",
),
ik_zone_code: IndemniteKilometriqueMontagne,
approval_code_1: PasAgrementRadio,
approval_code_2_raw: NumericString(
"0",
),
approval_code_3_raw: NumericString(
"0",
),
invoice_signature_permission: NumericString(
"1",
),
lot_signature_permission: NumericString(
"1",
),
},
),
},
],
}

View File

@ -1,97 +0,0 @@
---
source: crates/fsv/tests/test_ssv.rs
expression: cps_blocks
---
Data {
blocks: [
DataBlock {
header: BlockHeader {
group_id: GroupId(
1,
),
data_size: 53,
},
content: LireCartePS_Group1_Holder(
Holder {
card_type: CPS,
national_id_type: RPPS,
national_id: AlphaNumericString(
"99700593686",
),
national_id_key: AlphaNumericString(
"6",
),
civility_code: Monsieur,
holder_lastname: AlphaNumericString(
"PHARMOFFICE RPPS0059368",
),
holder_firstname: AlphaNumericString(
"GILBERT",
),
category_card: Unknown,
},
),
},
DataBlock {
header: BlockHeader {
group_id: GroupId(
2,
),
data_size: 93,
},
content: LireCartePS_Group2_Situation(
Situation {
id: RawByte(
1,
),
practice_mode: Liberal,
practice_status_raw: NumericString(
"1",
),
activity_sector: PharmacieDOfficine,
structure_id_type: FINESS,
structure_id: AlphaNumericString(
"0B0246286",
),
structure_id_key: NumericString(
"6",
),
structure_name: AlphaNumericString(
"PHARMACIE DE LA GARE ROUTIERE24628",
),
ps_billing_number: NumericString(
"00209368",
),
ps_billing_number_key: NumericString(
"0",
),
ps_replacement_number: AlphaNumericString(
"",
),
ps_replacement_number_key: NumericString(
"0",
),
convention_code: Conventionne,
specialty_code: PharmacieDOfficine,
rate_zone_code_raw: NumericString(
"10",
),
ik_zone_code: PasDIndemniteKilometrique,
approval_code_1: PasAgrementRadio,
approval_code_2_raw: NumericString(
"0",
),
approval_code_3_raw: NumericString(
"0",
),
invoice_signature_permission: NumericString(
"1",
),
lot_signature_permission: NumericString(
"1",
),
},
),
},
],
}

View File

@ -1,174 +0,0 @@
use anyhow::Result;
use common::read_cps;
use fsv::ssv::{Error, SSVErrorCodes, SSV};
use insta::assert_debug_snapshot;
mod common {
use super::*;
use std::env;
use fsv::fsv_parsing::Data;
use fsv_sys::SupportedFsvVersion;
use utils::config::load_config;
pub fn init() -> Result<SSV> {
load_config(None)?;
let sesam_ini_path = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set");
let lib = SSV::new(SupportedFsvVersion::V1_40_13)?;
lib.init_library(&sesam_ini_path)?;
Ok(lib)
}
pub fn read_cps() -> Result<Data> {
let lib = init()?;
Ok(lib.read_professional_card("1234")?)
}
}
// ---------------- PHARMA KIT ----------------
#[test]
#[ignore]
fn test_read_cps_pharma_kit_titulaire() -> Result<()> {
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_pharma_kit_adjointe() -> Result<()> {
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_pharma_kit_employe_aa() -> Result<()> {
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_pharma_kit_infirmiere() -> Result<()> {
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
// ---------------- DEFAULT KIT ----------------
#[test]
#[ignore]
fn test_read_cps_default_kit_doc() -> Result<()> {
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_default_kit_directeur() -> Result<()> {
let error = read_cps().expect_err("This card should be opposed and not be readable");
match error.downcast_ref::<Error>() {
Some(Error::SSVError(e)) => {
match e {
SSVErrorCodes::CPSInvalid => { Ok(()) },
_ => panic!("Expected SSVErrorCodes::CPSInvalid, got {:?}", e),
}
},
_ => panic!("Expected Error::SSVError got {:?}", error),
}
}
#[test]
#[ignore]
fn test_read_cps_default_kit_employee_opposee() -> Result<()> {
let error = read_cps().expect_err("This card should be opposed and not be readable");
match error.downcast_ref::<Error>() {
Some(Error::SSVError(e)) => {
match e {
SSVErrorCodes::SSVInternalError => { Ok(()) },
_ => panic!("Expected SSVErrorCodes::SSVInternalError, got {:?}", e),
}
},
_ => panic!("Expected Error::SSVError got {:?}", error),
}
}
#[test]
#[ignore]
fn test_read_cps_default_kit_doc_maximaxima() -> Result<()> {
// TODO : debug this card (ConventionCode value is 9)
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_default_kit_orthophoniste() -> Result<()> {
let error = read_cps().expect_err("This card should be opposed and not be readable");
match error.downcast_ref::<Error>() {
Some(Error::SSVError(e)) => {
match e {
SSVErrorCodes::SSVInternalError => { Ok(()) },
_ => panic!("Expected SSVErrorCodes::SSVInternalError, got {:?}", e),
}
},
_ => panic!("Expected Error::SSVError got {:?}", error),
}
}
// ---------------- CENTRE SANTÉ ----------------
#[test]
#[ignore]
fn test_read_cps_centre_sante_kit_directeur() -> Result<()> {
// TODO : debug this card (ConventionCode value is 9)
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_centre_sante_kit_doc() -> Result<()> {
// TODO : debug this card (ConventionCode value is 9)
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_centre_sante_kit_dentiste() -> Result<()> {
// TODO : debug this card (ConventionCode value is 9)
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_centre_sante_kit_assist_vladimir() -> Result<()> {
// TODO : debug this card (ConventionCode value is 9)
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
Ok(())
}
#[test]
#[ignore]
fn test_read_cps_centre_sante_kit_assist_marie() -> Result<()> {
// TODO : debug this card (ConventionCode value is 9)
let cps_blocks = read_cps()?;
assert_debug_snapshot!(cps_blocks);
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');