Implémentation du parsing des données issues de la lecture de la carte CPS (SSV_LireCartePS) #77

Open
florian_briand wants to merge 6 commits from feat/38-fsv-lire-carte-ps-parsing into feat/38-fsv-lire-config-parsing
15 changed files with 1638 additions and 23 deletions

View File

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

View File

@ -91,6 +91,20 @@ 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 `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` > - 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 ## 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 : 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

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

View File

@ -4,6 +4,7 @@ use deku::{deku_derive, DekuError};
use super::{ size_read, map_bytes_to_lossy_string }; use super::{ size_read, map_bytes_to_lossy_string };
pub mod ssv_lire_carte_ps;
pub mod ssv_lire_config; pub mod ssv_lire_config;
/// # Convert a DataField to a specific type /// # Convert a DataField to a specific type
@ -16,10 +17,29 @@ where
{ {
let text = String::from_utf8(data_field.data) let text = String::from_utf8(data_field.data)
.map_err(|e| DekuError::Parse(e.to_string().into()))?; .map_err(|e| DekuError::Parse(e.to_string().into()))?;
T::from_str(&text) text.parse::<T>()
.map_err(|e| DekuError::Parse(e.to_string().into())) .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 TYPES -------------------
/// # Data field structure /// # Data field structure
@ -37,18 +57,24 @@ struct DataField {
#[deku_derive(DekuRead)] #[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
/// # Numeric string /// # Data field: Numeric string (x CN)
/// TODO: check if all the characters are numeric /// TODO: check if all the characters are numeric
pub struct NumericString( pub struct NumericString(
#[deku(map = "map_from_data_field")] #[deku(map = "map_from_data_field")]
String pub String
); );
impl From<&str> for NumericString {
fn from(s: &str) -> Self {
NumericString(s.to_string())
}
}
#[deku_derive(DekuRead)] #[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
/// # Data field: Alphanumeric string (x CA/CE)
pub struct AlphaNumericString( pub struct AlphaNumericString(
#[deku(map = "map_from_data_field")] #[deku(map = "map_from_data_field")]
String pub String
); );
impl From<&str> for AlphaNumericString { impl From<&str> for AlphaNumericString {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
@ -56,6 +82,22 @@ 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)] #[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[deku(endian = "big")] #[deku(endian = "big")]
@ -99,4 +141,13 @@ mod test {
assert_eq!(software_version.version, "07"); assert_eq!(software_version.version, "07");
assert_eq!(software_version.revision, "20"); 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

@ -0,0 +1,733 @@
//! # 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(); 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.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] #[test]
@ -289,6 +289,7 @@ mod tests {
assert_eq!(content.name.0.0, "Gemalto PC Twin Reader (645D94C3) 00 00", "Reader Name"); 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"); assert_eq!(content.card_type.0.0, "2", "Card Type");
}, },
_ => panic!("Unexpected data block type"),
} }
} }
} }

View File

@ -33,6 +33,26 @@ fn size_read<R: std::io::Read + std::io::Seek>(reader: &mut Reader<R>) -> Result
Ok(size) 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 /// # Map bytes to a lossy string
/// This function is used to map bytes to a string, ignoring invalid UTF-8 characters /// This function is used to map bytes to a string, ignoring invalid UTF-8 characters
@ -43,3 +63,70 @@ fn map_bytes_to_lossy_string(data: &[u8]) -> Result<String, DekuError> {
let version: String = String::from_utf8_lossy(data).to_string(); let version: String = String::from_utf8_lossy(data).to_string();
Ok(version) 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

@ -14,7 +14,7 @@ use fsv_sys::{
mod errors_ssv; mod errors_ssv;
use errors_ssv::SSVErrorCodes; pub use errors_ssv::SSVErrorCodes;
use crate::fsv_parsing::prelude::*; use crate::fsv_parsing::prelude::*;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -76,7 +76,7 @@ impl SSV {
/// # Read the CPS card /// # Read the CPS card
/// Implement: SSV_LireCartePS /// Implement: SSV_LireCartePS
pub fn read_professional_card(&self, pin_code: &str) -> Result<(), Error> { pub fn read_professional_card(&self, pin_code: &str) -> Result<Data, Error> {
let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00"; let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00";
let pin_code = CString::new(pin_code).expect("CString::new failed"); let pin_code = CString::new(pin_code).expect("CString::new failed");
@ -111,12 +111,13 @@ impl SSV {
let error = SSVErrorCodes::from(result); let error = SSVErrorCodes::from(result);
return Err(Error::SSVError(error)); return Err(Error::SSVError(error));
} }
// Print 10 bytes of the buffer // Parse the buffer into a Data struct
let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, 10) }; let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, out_buffer_size) };
println!("{:?}", buffer); let (_rest, cps_blocks) = Data::from_bytes((buffer, 0)).unwrap();
// Free memory // Free memory
unsafe { libc::free(out_buffer_ptr) }; unsafe { libc::free(out_buffer_ptr) };
Ok(()) Ok(cps_blocks)
} }
/// # Get the configuration of the SSV library /// # Get the configuration of the SSV library
@ -157,7 +158,7 @@ mod tests {
use utils::config::load_config; use utils::config::load_config;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crate::fsv_parsing::blocks::DataGroup; use crate::fsv_parsing::{blocks::DataGroup, groups::ssv_lire_carte_ps::{group_1_holder::CardPSType, group_2_situation::{PracticeMode, SpecialtyCode}}};
use super::*; use super::*;
@ -183,11 +184,32 @@ mod tests {
} }
#[test] #[test]
#[ignore="WARNING: Read the card with PIN 1234 - Risk of blocking the card"] #[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)
"]
fn test_read_professional_card_good_pin() -> Result<()> { fn test_read_professional_card_good_pin() -> Result<()> {
let lib = setup::init()?; let lib = setup::init()?;
let pin_code = "1234"; let pin_code = "1234";
lib.read_professional_card(pin_code)?; 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");
Ok(()) Ok(())
} }

View File

@ -0,0 +1,97 @@
---
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

@ -0,0 +1,97 @@
---
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

@ -0,0 +1,97 @@
---
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

@ -0,0 +1,97 @@
---
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

@ -0,0 +1,97 @@
---
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

@ -0,0 +1,174 @@
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(())
}