Krys4lide/crates/sesam-vitale/src/cps.rs

401 lines
16 KiB
Rust
Raw Normal View History

use libc::{c_void, size_t};
use std::ffi::CString;
use std::ptr;
use thiserror::Error;
use crate::libssv::{self, SSV_LireCartePS};
use crate::ssv_memory::{decode_ssv_memory, Block, SSVMemoryError};
#[derive(Error, Debug)]
pub enum CartePSError {
#[error("Unknown (group, field) pair: ({group}, {field})")]
UnknownGroupFieldPair { group: u16, field: u16 },
#[error("CString creation error: {0}")]
CString(#[from] std::ffi::NulError),
#[error("Unable to get the last situation while parsing a CartePS")]
InvalidLastSituation,
#[error(transparent)]
SSVMemory(#[from] SSVMemoryError),
#[error(transparent)]
SSVLibErrorCode(#[from] libssv::LibSSVError),
}
#[derive(Debug, Default)]
pub struct CartePS {
titulaire: TitulairePS,
situations: Vec<SituationPS>,
}
2024-08-02 22:58:32 +02:00
// 1. CB = Caractères Binaires »
// 2. CE = Caractères « Etendus » (ISO 8859-1)
// 3. CA = Caractères Alphanumériques (ASCII?)
// 4. CN = Caractères Numériques
#[derive(Debug, Default)]
struct TitulairePS {
type_de_carte_ps: String, // CN
type_d_identification_nationale: String, // CN
numero_d_identification_nationale: String, // CE - 8 -> 30
cle_du_numero_d_identification_nationale: String, // CN
code_civilite: String, // CN
nom_du_ps: String, // CE - 27
prenom_du_ps: String, // CE - 27
categorie_carte: char, // CA
}
#[derive(Debug, Default)]
struct SituationPS {
numero_logique_de_la_situation_de_facturation_du_ps: u8,
mode_d_exercice: String,
statut_d_exercice: String,
secteur_d_activite: String,
type_d_identification_structure: String,
numero_d_identification_structure: String,
cle_du_numero_d_identification_structure: String,
raison_sociale_structure: String,
numero_d_identification_de_facturation_du_ps: String,
cle_du_numero_d_identification_de_facturation_du_ps: String,
numero_d_identification_du_ps_remplaçant: String,
cle_du_numero_d_identification_du_ps_remplaçant: String,
code_conventionnel: String,
code_specialite: String,
code_zone_tarifaire: String,
code_zone_ik: String,
code_agrement_1: String,
code_agrement_2: String,
code_agrement_3: String,
habilitation_à_signer_une_facture: String,
habilitation_à_signer_un_lot: String,
}
pub fn lire_carte(code_pin: &str, lecteur: &str) -> Result<CartePS, CartePSError> {
let resource_ps = CString::new(lecteur)?;
let resource_reader = CString::new("")?;
let card_number = CString::new(code_pin)?;
let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0;
let mut hex_values: &[u8] = &[];
unsafe {
let result = SSV_LireCartePS(
resource_ps.as_ptr(),
resource_reader.as_ptr(),
card_number.as_ptr(),
&mut buffer,
&mut size,
);
println!("SSV_LireCartePS result: {}", result);
if result != 0 {
return Err(libssv::LibSSVError::StandardErrorCode {
code: result,
function: "SSV_LireCartePS",
}
.into());
}
if !buffer.is_null() {
hex_values = std::slice::from_raw_parts(buffer as *const u8, size);
libc::free(buffer);
}
}
let groups =
decode_ssv_memory(hex_values, hex_values.len()).map_err(CartePSError::SSVMemory)?;
decode_carte_ps(groups)
}
fn get_last_mut_situation(carte_ps: &mut CartePS) -> Result<&mut SituationPS, CartePSError> {
carte_ps
.situations
.last_mut()
.ok_or(CartePSError::InvalidLastSituation)
}
fn decode_carte_ps(groups: Vec<Block>) -> Result<CartePS, CartePSError> {
let mut carte_ps = CartePS::default();
for group in groups {
2024-08-02 23:02:21 +02:00
for field in group.content {
match (group.id, field.id) {
(1, 1) => {
carte_ps.titulaire.type_de_carte_ps =
String::from_utf8_lossy(field.content).to_string();
}
(1, 2) => {
carte_ps.titulaire.type_d_identification_nationale =
String::from_utf8_lossy(field.content).to_string();
}
(1, 3) => {
carte_ps.titulaire.numero_d_identification_nationale =
String::from_utf8_lossy(field.content).to_string();
}
(1, 4) => {
carte_ps.titulaire.cle_du_numero_d_identification_nationale =
String::from_utf8_lossy(field.content).to_string();
}
(1, 5) => {
carte_ps.titulaire.code_civilite =
String::from_utf8_lossy(field.content).to_string();
}
(1, 6) => {
carte_ps.titulaire.nom_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(1, 7) => {
carte_ps.titulaire.prenom_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(1, 8) => {
let byte = field.content[0];
carte_ps.titulaire.categorie_carte = byte as char;
}
(2..=16, 1) => {
carte_ps.situations.push(SituationPS::default());
get_last_mut_situation(&mut carte_ps)?
.numero_logique_de_la_situation_de_facturation_du_ps = field.content[0];
}
(2..=16, 2) => {
get_last_mut_situation(&mut carte_ps)?.mode_d_exercice =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 3) => {
get_last_mut_situation(&mut carte_ps)?.statut_d_exercice =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 4) => {
get_last_mut_situation(&mut carte_ps)?.secteur_d_activite =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 5) => {
get_last_mut_situation(&mut carte_ps)?.type_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 6) => {
get_last_mut_situation(&mut carte_ps)?.numero_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 7) => {
get_last_mut_situation(&mut carte_ps)?
.cle_du_numero_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 8) => {
get_last_mut_situation(&mut carte_ps)?.raison_sociale_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 9) => {
get_last_mut_situation(&mut carte_ps)?
.numero_d_identification_de_facturation_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 10) => {
get_last_mut_situation(&mut carte_ps)?
.cle_du_numero_d_identification_de_facturation_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 11) => {
get_last_mut_situation(&mut carte_ps)?
.numero_d_identification_du_ps_remplaçant =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 12) => {
get_last_mut_situation(&mut carte_ps)?
.cle_du_numero_d_identification_du_ps_remplaçant =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 13) => {
get_last_mut_situation(&mut carte_ps)?.code_conventionnel =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 14) => {
get_last_mut_situation(&mut carte_ps)?.code_specialite =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 15) => {
get_last_mut_situation(&mut carte_ps)?.code_zone_tarifaire =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 16) => {
get_last_mut_situation(&mut carte_ps)?.code_zone_ik =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 17) => {
get_last_mut_situation(&mut carte_ps)?.code_agrement_1 =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 18) => {
get_last_mut_situation(&mut carte_ps)?.code_agrement_2 =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 19) => {
get_last_mut_situation(&mut carte_ps)?.code_agrement_3 =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 20) => {
get_last_mut_situation(&mut carte_ps)?.habilitation_à_signer_une_facture =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 21) => {
get_last_mut_situation(&mut carte_ps)?.habilitation_à_signer_un_lot =
String::from_utf8_lossy(field.content).to_string();
}
_ => {
return Err(CartePSError::UnknownGroupFieldPair {
group: group.id,
field: field.id,
});
}
}
}
}
Ok(carte_ps)
}
#[cfg(test)]
mod test_decode_carte_ps {
use super::*;
#[test]
fn test_francoise_pharmacien0052419() {
let bytes: &[u8] = &[
2024-08-02 22:58:32 +02:00
0, 1, 51, // Block 01, Content size 51
1, 48, // Field 01, Content size 1
1, 56, // Field 02, Content size 1
11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // Field 03, Content size 11
1, 52, // Field 04, Content size 1
2, 50, 50, // Field 05, Content size 2
17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49,
57, // Field 06, Content size 17
9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // Field 07, Content size 9
1, 84, // Field 08, Content size 1
0, 2, 83, // Block 02, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
];
let blocks = decode_ssv_memory(bytes, bytes.len()).unwrap();
let carte_ps = decode_carte_ps(blocks).unwrap();
assert_eq!(carte_ps.titulaire.type_de_carte_ps, "0");
assert_eq!(carte_ps.titulaire.type_d_identification_nationale, "8");
assert_eq!(
carte_ps.titulaire.numero_d_identification_nationale,
"99700524194"
);
assert_eq!(
carte_ps.titulaire.cle_du_numero_d_identification_nationale,
"4"
);
assert_eq!(carte_ps.titulaire.code_civilite, "22");
assert_eq!(carte_ps.titulaire.nom_du_ps, "PHARMACIEN0052419");
assert_eq!(carte_ps.titulaire.prenom_du_ps, "FRANCOISE");
assert_eq!(carte_ps.titulaire.categorie_carte, 'T');
assert_eq!(carte_ps.situations.len(), 1);
assert_eq!(
carte_ps.situations[0].numero_logique_de_la_situation_de_facturation_du_ps,
1
);
assert_eq!(carte_ps.situations[0].mode_d_exercice, "0");
assert_eq!(carte_ps.situations[0].statut_d_exercice, "1");
assert_eq!(carte_ps.situations[0].secteur_d_activite, "86");
assert_eq!(carte_ps.situations[0].type_d_identification_structure, "1");
assert_eq!(
carte_ps.situations[0].numero_d_identification_structure,
"0B0221958"
);
assert_eq!(
carte_ps.situations[0].cle_du_numero_d_identification_structure,
"8"
);
assert_eq!(
carte_ps.situations[0].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
assert_eq!(
carte_ps.situations[0].numero_d_identification_de_facturation_du_ps,
"00202419"
);
assert_eq!(
carte_ps.situations[0].cle_du_numero_d_identification_de_facturation_du_ps,
"8"
);
assert_eq!(
carte_ps.situations[0].numero_d_identification_du_ps_remplaçant,
""
);
assert_eq!(
carte_ps.situations[0].cle_du_numero_d_identification_du_ps_remplaçant,
"0"
);
assert_eq!(carte_ps.situations[0].code_conventionnel, "1");
assert_eq!(carte_ps.situations[0].code_specialite, "50");
assert_eq!(carte_ps.situations[0].code_zone_tarifaire, "10");
assert_eq!(carte_ps.situations[0].code_zone_ik, "00");
assert_eq!(carte_ps.situations[0].code_agrement_1, "0");
assert_eq!(carte_ps.situations[0].code_agrement_2, "0");
assert_eq!(carte_ps.situations[0].code_agrement_3, "0");
assert_eq!(
carte_ps.situations[0].habilitation_à_signer_une_facture,
"1"
);
assert_eq!(carte_ps.situations[0].habilitation_à_signer_un_lot, "1");
}
#[test]
fn test_multiple_situations() {
let bytes: &[u8] = &[
0, 1, 51, // Block 01, Content size 51
1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, 50, 17, 80,
72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, 70, 82, 65, 78, 67,
79, 73, 83, 69, 1, 84, 0, 2, 83, // Block 02, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 0, 3,
83, // Block 03, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 0, 4,
83, // Block 04, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
];
let blocks = decode_ssv_memory(bytes, bytes.len()).unwrap();
let carte_ps = decode_carte_ps(blocks).unwrap();
assert_eq!(carte_ps.situations.len(), 3);
assert_eq!(
carte_ps.situations[0].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
assert_eq!(
carte_ps.situations[1].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
assert_eq!(
carte_ps.situations[2].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
}
#[test]
#[should_panic]
fn test_missing_field() {
todo!();
}
#[test]
#[should_panic]
fn test_unknown_group_field_pair() {
todo!();
}
#[test]
#[should_panic]
fn test_invalid_field_format() {
todo!();
}
}