Compare commits

...

4 Commits

5 changed files with 136 additions and 82 deletions

View File

@ -2,13 +2,6 @@ use libc::{c_void, size_t};
use std::ffi::CString;
use std::ptr;
/*
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
*/
use crate::libssv::SSV_LireCartePS;
use crate::ssv_memory::{decode_ssv_memory, Block};
@ -18,6 +11,10 @@ pub struct CartePS {
situations: Vec<SituationPS>,
}
// 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
@ -55,7 +52,7 @@ struct SituationPS {
habilitation_à_signer_un_lot: String,
}
pub fn lire_carte(code_pin: &str, lecteur: &str) -> CartePS {
pub fn lire_carte(code_pin: &str, lecteur: &str) -> Result<CartePS, String> {
let resource_ps = CString::new(lecteur).expect("CString::new failed");
let resource_reader = CString::new("").expect("CString::new failed");
let card_number = CString::new(code_pin).expect("CString::new failed");
@ -82,11 +79,11 @@ pub fn lire_carte(code_pin: &str, lecteur: &str) -> CartePS {
decode_carte_ps(groups)
}
fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
fn decode_carte_ps(groups: Vec<Block>) -> Result<CartePS, String> {
let mut carte_ps = CartePS::default();
for group in groups {
for (field_index, field) in group.content.iter().enumerate() {
match (group.id, field_index + 1) {
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();
@ -119,7 +116,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
let byte = field.content[0];
carte_ps.titulaire.categorie_carte = byte as char;
}
(2, 1) => {
(2..=16, 1) => {
carte_ps.situations.push(SituationPS::default());
carte_ps
.situations
@ -127,19 +124,19 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.unwrap()
.numero_logique_de_la_situation_de_facturation_du_ps = field.content[0];
}
(2, 2) => {
(2..=16, 2) => {
carte_ps.situations.last_mut().unwrap().mode_d_exercice =
String::from_utf8_lossy(field.content).to_string();
}
(2, 3) => {
(2..=16, 3) => {
carte_ps.situations.last_mut().unwrap().statut_d_exercice =
String::from_utf8_lossy(field.content).to_string();
}
(2, 4) => {
(2..=16, 4) => {
carte_ps.situations.last_mut().unwrap().secteur_d_activite =
String::from_utf8_lossy(field.content).to_string();
}
(2, 5) => {
(2..=16, 5) => {
carte_ps
.situations
.last_mut()
@ -147,7 +144,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.type_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2, 6) => {
(2..=16, 6) => {
carte_ps
.situations
.last_mut()
@ -155,7 +152,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.numero_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2, 7) => {
(2..=16, 7) => {
carte_ps
.situations
.last_mut()
@ -163,7 +160,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.cle_du_numero_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2, 8) => {
(2..=16, 8) => {
carte_ps
.situations
.last_mut()
@ -171,7 +168,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.raison_sociale_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2, 9) => {
(2..=16, 9) => {
carte_ps
.situations
.last_mut()
@ -179,7 +176,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.numero_d_identification_de_facturation_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(2, 10) => {
(2..=16, 10) => {
carte_ps
.situations
.last_mut()
@ -187,7 +184,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.cle_du_numero_d_identification_de_facturation_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(2, 11) => {
(2..=16, 11) => {
carte_ps
.situations
.last_mut()
@ -195,7 +192,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.numero_d_identification_du_ps_remplaçant =
String::from_utf8_lossy(field.content).to_string();
}
(2, 12) => {
(2..=16, 12) => {
carte_ps
.situations
.last_mut()
@ -203,35 +200,35 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.cle_du_numero_d_identification_du_ps_remplaçant =
String::from_utf8_lossy(field.content).to_string();
}
(2, 13) => {
(2..=16, 13) => {
carte_ps.situations.last_mut().unwrap().code_conventionnel =
String::from_utf8_lossy(field.content).to_string();
}
(2, 14) => {
(2..=16, 14) => {
carte_ps.situations.last_mut().unwrap().code_specialite =
String::from_utf8_lossy(field.content).to_string();
}
(2, 15) => {
(2..=16, 15) => {
carte_ps.situations.last_mut().unwrap().code_zone_tarifaire =
String::from_utf8_lossy(field.content).to_string();
}
(2, 16) => {
(2..=16, 16) => {
carte_ps.situations.last_mut().unwrap().code_zone_ik =
String::from_utf8_lossy(field.content).to_string();
}
(2, 17) => {
(2..=16, 17) => {
carte_ps.situations.last_mut().unwrap().code_agrement_1 =
String::from_utf8_lossy(field.content).to_string();
}
(2, 18) => {
(2..=16, 18) => {
carte_ps.situations.last_mut().unwrap().code_agrement_2 =
String::from_utf8_lossy(field.content).to_string();
}
(2, 19) => {
(2..=16, 19) => {
carte_ps.situations.last_mut().unwrap().code_agrement_3 =
String::from_utf8_lossy(field.content).to_string();
}
(2, 20) => {
(2..=16, 20) => {
carte_ps
.situations
.last_mut()
@ -239,7 +236,7 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.habilitation_à_signer_une_facture =
String::from_utf8_lossy(field.content).to_string();
}
(2, 21) => {
(2..=16, 21) => {
carte_ps
.situations
.last_mut()
@ -247,11 +244,16 @@ fn decode_carte_ps(groups: Vec<Block>) -> CartePS {
.habilitation_à_signer_un_lot =
String::from_utf8_lossy(field.content).to_string();
}
_ => (),
_ => {
return Err(format!(
"Unknown (group, field) pair: ({}, {})",
group.id, field.id
))
}
}
}
}
carte_ps
Ok(carte_ps)
}
#[cfg(test)]
@ -261,23 +263,24 @@ mod test_decode_carte_ps {
#[test]
fn test_francoise_pharmacien0052419() {
let bytes: &[u8] = &[
0, 1, 51, // 3
1, 48, // 2
1, 56, // 2
11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12
1, 52, // 2
2, 50, 50, // 3
17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18
9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10
1, 84, // 2
// total: 54
0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56,
1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69,
50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53,
48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
0, 1, 51, // Block 01, Content size 51
1, 48, // Field 01, Content size 1
1, 56, // Field 02, Content size 1
11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // Field 03, Content size 11
1, 52, // Field 04, Content size 1
2, 50, 50, // Field 05, Content size 2
17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49,
57, // Field 06, Content size 17
9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // Field 07, Content size 9
1, 84, // Field 08, Content size 1
0, 2, 83, // Block 02, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
];
let blocks = decode_ssv_memory(bytes, bytes.len());
let carte_ps = decode_carte_ps(blocks);
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");
@ -344,4 +347,62 @@ mod test_decode_carte_ps {
);
assert_eq!(carte_ps.situations[0].habilitation_à_signer_un_lot, "1");
}
#[test]
fn test_multiple_situations() {
let bytes: &[u8] = &[
0, 1, 51, // Block 01, Content size 51
1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, 50, 17, 80,
72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, 70, 82, 65, 78, 67,
79, 73, 83, 69, 1, 84, 0, 2, 83, // Block 02, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 0, 3,
83, // Block 03, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 0, 4,
83, // Block 04, Content size 83
1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56, 1, 56, 24,
80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69, 50, 50, 49,
57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53, 48, 2, 49,
48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
];
let blocks = decode_ssv_memory(bytes, bytes.len());
let carte_ps = decode_carte_ps(blocks).unwrap();
assert_eq!(carte_ps.situations.len(), 3);
assert_eq!(
carte_ps.situations[0].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
assert_eq!(
carte_ps.situations[1].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
assert_eq!(
carte_ps.situations[2].raison_sociale_structure,
"PHARMACIE DU CENTRE22195"
);
}
#[test]
#[should_panic]
fn test_missing_field() {
todo!();
}
#[test]
#[should_panic]
fn test_unknown_group_field_pair() {
todo!();
}
#[test]
#[should_panic]
fn test_invalid_field_format() {
todo!();
}
}

View File

@ -1,9 +1,7 @@
/**
* libssv.rs
*
* Low level bindings to the SSVLIB dynamic library.
* TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate
*/
/// libssv.rs
///
/// Low level bindings to the SSVLIB dynamic library.
// TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate
use libc::{c_char, c_ushort, c_void, size_t};
#[cfg_attr(target_os = "linux", link(name = "ssvlux64"))]
@ -22,4 +20,4 @@ extern "C" {
TTailleDonneesSortie: *mut size_t,
) -> c_ushort;
}
/* TODO : replace void* by Rust struct : https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs */
// TODO : replace void* by Rust struct : https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs

View File

@ -5,5 +5,4 @@ mod ssvlib_demo;
fn main() {
ssvlib_demo::demo();
// XXX
}

View File

@ -1,30 +1,27 @@
/// # SSV Memory
/// Provide functions to manipulate raw memory from SSV library.
///
use std::convert::TryFrom;
// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ?
#[derive(PartialEq, Debug)]
struct ElementSize {
pub size: usize,
pub pad: usize,
}
// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ?
impl TryFrom<&[u8]> for ElementSize {
type Error = &'static str;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
/* Longueur:
* - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet
* - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur
*/
if bytes.is_empty() {
return Err("Empty bytes input");
}
let mut element_size = ElementSize { size: 0, pad: 1 };
// Longueur:
// - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet
// - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur
if bytes[0] & 0b1000_0000 == 0 {
// Size coded on 1 byte
element_size.size = bytes[0] as usize;
@ -71,9 +68,12 @@ impl<'a> From<&'a [u8]> for Block<'a> {
let mut field_offset = 0;
// While there is still content to read, parse Fields
let mut content = Vec::new();
let mut field_id = 1;
while field_offset < block_size {
let field: Field<'a> = raw_content[field_offset..].into();
let mut field: Field<'a> = raw_content[field_offset..].into();
field.id = field_id;
field_offset += field.size;
field_id += 1;
content.push(field);
}
Block {
@ -86,6 +86,7 @@ impl<'a> From<&'a [u8]> for Block<'a> {
#[derive(Debug)]
pub struct Field<'a> {
pub id: u16,
pub size: usize,
pub content: &'a [u8],
}
@ -95,6 +96,7 @@ impl<'a> From<&'a [u8]> for Field<'a> {
let ElementSize { size, pad } = bytes.try_into().unwrap();
let contenu = &bytes[pad..pad + size];
Field {
id: 0,
size: pad + size,
content: contenu,
}

View File

@ -1,16 +1,11 @@
/// High level API for the SSV library,
/// based on the low level bindings in libssv.rs.
extern crate dotenv;
use libc::{c_void, size_t};
/**
* High level API for the SSV library,
* based on the low level bindings in libssv.rs.
*
*/
use std::path::PathBuf;
use std::ffi::CString;
use std::ptr;
use std::env;
use std::ffi::CString;
use std::path::PathBuf;
use std::ptr;
use crate::cps::lire_carte;
use crate::libssv::{SSV_InitLIB2, SSV_LireConfig};
@ -44,20 +39,19 @@ fn ssv_lire_config() {
}
pub fn demo() {
/*
* TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env`
* Maybe we could use a system standard config path to store a config file
*/
// TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env`
// Maybe we could use a system standard config path to store a config file
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let manifest_path = PathBuf::from(manifest_dir);
dotenv::from_path(manifest_path.join(".env")).ok();
println!("------- Demo for the SSV library 2 --------");
println!("------- Demo for the SSV library --------");
ssv_init_lib_2();
let code_pin = "1234";
let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0";
let carte_ps = lire_carte(code_pin, lecteur);
let carte_ps = lire_carte(code_pin, lecteur).unwrap();
println!("CartePS: {:#?}", carte_ps);
ssv_lire_config();