47 Commits

Author SHA1 Message Date
b61634fea9 2024-08-15 17:19:40 +02:00
6b6658f56d 2024-08-15 14:36:55 +02:00
d56f7a2249 2024-08-14 15:48:24 +02:00
2b45f5aa7e 2024-08-14 15:33:56 +02:00
a3fef1c38c 2024-08-14 13:50:09 +02:00
a64876cfa0 init 2024-08-14 13:18:04 +02:00
5269dd7789 Merge pull request 'refactor: Used askama_axum::Template' (#51) from askama_axum_template into main
### Détails

Changement de `askama::Template` à `askama_axum::Template`.

### Pourquoi ?

Pour supprimer les `into_response()` et ainsi réduire les imports

### Documentation

https://djc.github.io/askama/integrations.html#axum-integration

Reviewed-on: P4Pillon/Krys4lide#51
Reviewed-by: florian_briand <florian.briand@digital-engine.info>
2024-08-10 17:51:51 +02:00
c3f97564d6 refactor: Used askama_axum::Template
docs: https://djc.github.io/askama/integrations.html#axum-integration
2024-08-10 16:59:43 +02:00
69a2d11501 Merge pull request 'Configurer le re-build automatique de l'app front lors de changements' (#47) from html_auto_reload into main
### Détails

Nous avons plusieurs besoins :
- reconstruire le serveur lorsque des fichiers _Rust_ ont été modifiés
- recharger les fichiers HTML lors d'un changement directement dans le navigateur

Le fichier `.ignore` permet d'indiquer à `systemfd` de ne pas surveiller l'état de ses fichiers.

Actuellement lors d'une modification, on se retrouve sur la page d'accueil vu que l'url reste toujours la même lors d'un changement de page.

### Pourquoi ?

Pour être plus rapide lors du développement et ainsi ne pas à avoir à relancer les commandes trop régulièrement.

### Documentation

Documentation des librairies
- [auto-reload sur Axum](6bd6556385/examples/auto-reload/README.md)
- [tower_livereload](https://docs.rs/tower-livereload/latest/tower_livereload/)

### Todo

- [x] Documenter un peu plus
- [x] Rendre plus lisible `main.rs`

Fix #44

Reviewed-on: P4Pillon/Krys4lide#47
Reviewed-by: florian_briand <florian.briand@digital-engine.info>
2024-08-09 15:50:54 +02:00
fb201f9d5d refacto: extract livereload layer setup into a function
Co-authored-by: kosssi <github@fafaru.com>
2024-08-09 13:58:12 +02:00
dcb4a7680e refacto: extract TCP Listener building into a dedicated function
Co-authored-by: kosssi <github@fafaru.com>
2024-08-09 12:27:31 +02:00
0c8e417f11 docs: Move documentation on code 2024-08-09 01:41:03 +02:00
73f45442b6 feat: Add dev dependencies with cargo
cargo add cargo-watch --dev --package app
cargo add systemfd --dev --package app
2024-08-09 01:29:59 +02:00
9c57b119ce docs: Ajout de documentation autour de l'auto-reload et du livereload 2024-08-09 00:35:36 +02:00
237bbe789f feat: Add livereload 2024-08-08 22:19:24 +02:00
1ae80c161f feat: Add auto-reload on development environment 2024-08-08 22:18:26 +02:00
668a91941b Merge pull request 'style: Format code with fmt' (#48) from fmt into main
### Détails

`fmt` permet de formater le code Rust, je pense que c'est une bonne chose que l'on utilise l'utilitaire aevc la commande `cargo fmt` nous pourrons mettre en place un test de validation des DAs quand nous aurons une CI avec la commande suivante `cargo fmt --all -- --check`.

### Pourquoi ?

Pour rendre le code plus lisible

### Documentation

https://rust-lang.github.io/rustfmt/

Reviewed-on: P4Pillon/Krys4lide#48
Reviewed-by: theo <theo.lettermann@gmail.com>
2024-08-08 15:08:03 +02:00
0eaf238735 style: Format code with fmt 2024-08-08 15:01:28 +02:00
b10fc30984 Merge pull request 'Ajout d'un système de pages pour la barre de navigation' (#42) from feat/8_implement_main_ui_part2 into main
Cette PR explore une organisation en "pages" pour le rendu des différents "onglets" de la navbar.

Contrairement à la PR précédente (#40), les composants ajoutés viennent de la bibliothèque Flowbite

En plus des pages, cette PR :
- remplace le texte de chargement Chargement... par des skeleton (sorte de placeholder visuels)
- utilise le système HTMX de hx-swap-oob pour changer le titre dynamiquement
- ajoutent les fichiers minifiés de de htmx / alpinejs / flowbite dans les assets, avec leur numéro de version

Reviewed-on: P4Pillon/Krys4lide#42
Reviewed-by: kosssi <simon@p4pillon.org>
Reviewed-by: theo <theo.lettermann@gmail.com>
2024-08-06 21:50:20 +02:00
e8f4c50ad0 refacto: add alpinejs, flowbite and htmx to app assets with explicit versions 2024-08-06 21:30:14 +02:00
e084372b44 feat: add page system behing navbar items and skeletons for loading 2024-08-06 21:19:24 +02:00
898ee32f9a Merge pull request 'Interface - Implémentation d'une première ébauche technique' (#40) from feat/8_implement_main_ui into main
Cette PR met concrètement en place une première interface combinant les technologies front choisies pour le projet.

- Setup de tailwindcss (+ documentation basique sur son usage)
- Implémentation d'un composant "complexe" de barre de navigation
- Gestion du responsive
- Combinaison de Askama (Jinja) + HTMX
- Usage de AlpineJS pour les micro-interactions (affichage des menus)

À suivre, dans la PR #42 : une interface moins "random" et plus orientée vers nos besoins

Contribue à #8

Reviewed-on: P4Pillon/Krys4lide#40
Reviewed-by: kosssi <simon@p4pillon.org>
Reviewed-by: theo <theo.lettermann@gmail.com>
2024-08-06 21:12:31 +02:00
f3495b8fb4 fixup! feat: make Nav and Profile menu dynamic 2024-08-06 21:11:04 +02:00
06e03011d8 fixup! feat: Setup Tailwind CSS 2024-08-06 21:11:04 +02:00
78bf81c301 feat: make Nav and Profile menu dynamic 2024-08-06 21:11:04 +02:00
aba6c101cb feat: replace vanilla JS by AlpineJS 2024-08-06 21:11:04 +02:00
23f85c5e92 feat: add navbar layout with some JS controls (with Alpine.js) 2024-08-06 21:11:04 +02:00
e057889403 refacto: nest axum templates routes 2024-08-06 21:11:04 +02:00
4a27dacd8e feat: Setup Tailwind CSS 2024-08-06 21:11:04 +02:00
a365e9206f Merge pull request 'docs: Add Tauri version' (#41) from tauri_version into main
### Détails

Spécifie la version de Tauri à installer dans la documentation

### Pourquoi ?

Il faut la bonne version de Tauri pour que ça fonctionne et que l'on soit raccord dans l'équipe

Reviewed-on: P4Pillon/Krys4lide#41
Reviewed-by: florian_briand <florian.briand@digital-engine.info>
2024-08-06 09:39:20 +02:00
4b96e6348e docs: Add Tauri version 2024-08-06 00:15:51 +02:00
4627f9540a Merge pull request 'Ajout de la lecture de carte CPS dans le moteur SESAM-Vitale' (#29) from feature_ssv_lire_carte_ps into main
Reviewed-on: P4Pillon/Krys4lide#29
Reviewed-by: theo <theo.lettermann@gmail.com>
2024-08-03 14:57:57 +02:00
83aef34750 fix: handle multiple situations on a CPS 2024-08-02 23:02:44 +02:00
9126d1311b feat: add a field.id 2024-08-02 23:02:21 +02:00
4d004afc5e feature: handle some errors in decode_carte_ps 2024-08-02 23:00:44 +02:00
723c06acd9 refactor: clean comments and docstring 2024-08-02 22:58:32 +02:00
e9ef6cbb4b feat: cleaning of all the ssv_memory and ssvlib_demo to get a cleaner cps::lire_carte API 2024-08-02 00:08:49 +02:00
0be0b08f89 feat: implement some more structured version of the memory decoding and the mapping to CPS fields 2024-08-01 23:20:11 +02:00
13c2f7573b feat: implement block and fields as Struct implementing From trait 2024-08-01 23:20:11 +02:00
4def46745d feat: add read_element function for raw bloc or field parsing 2024-08-01 23:19:22 +02:00
2e07f0b7d1 feat: add a function to read a "bloc / field size" in SSV memory 2024-08-01 23:19:22 +02:00
d65c869949 feat: add the structure to reprensent CPS fields returned by the lire_carte_ps function 2024-08-01 23:19:22 +02:00
f799f471bc feat: Implement a first version of the decode_zone_memoire function 2024-08-01 23:18:42 +02:00
e7f3322484 Merge pull request 'fix: add the about key to Gitea PR Template' (#32) from fix/#31_Gitea_PR_Template into main
Reviewed-on: P4Pillon/Krys4lide#32
Reviewed-by: theo <theo.lettermann@gmail.com>
2024-08-01 18:27:58 +02:00
147125eff4 fix: add the about key to Gitea PR Template 2024-08-01 00:01:11 +02:00
ad7a0f2594 Merge pull request 'Feature : mise en place de Clego avec un exemple d'implémentation crossplateforme de la librairie SESAM-Vitale SSV' (#23) from feature-setup-clego into main
Reviewed-on: P4Pillon/Krys4lide#23
Reviewed-by: kosssi <simon@p4pillon.org>
2024-07-29 19:43:50 +02:00
358a279f5c fix: make sesam-vitale build works even when having a lib.rs file 2024-07-29 19:43:03 +02:00
61 changed files with 4997 additions and 158 deletions

View File

@ -1,4 +1,5 @@
name: Demande de fusion (Pull Request) name: Demande de fusion (Pull Request)
about: Créez une demande de fusion pour partager votre travail avec le reste de l'équipe
body: body:
- type: markdown - type: markdown
attributes: attributes:

5
.ignore Normal file
View File

@ -0,0 +1,5 @@
# Ignorer les fichiers dont ne dépent pas la compilation
*.md
tailwind.config.js
*.example
scripts

1488
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,24 +12,54 @@ Logiciel de Pharmacie libre et open-source.
### Pré-requis ### Pré-requis
#### Tauri CLI
La CLI Tauri est nécessaire au lancement du client `desktop`. Elle peut être installée via Cargo : La CLI Tauri est nécessaire au lancement du client `desktop`. Elle peut être installée via Cargo :
```bash ```bash
cargo install tauri-cli cargo install tauri-cli --version "^2.0.0-beta"
``` ```
### Exécution de l'application cliente desktop #### Tailwindcss CLI
Le CLI Tailwindcss est nécessaire pour la génération du fichier `crates/app/assets/css/style.css`.
La documentation d'installation est disponible sur le site officiel de Tailwindcss : https://tailwindcss.com/blog/standalone-cli
La version actuellement utilisée est la [`v3.4.7`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.7)
#### SESAM-Vitale
La crate `sesam-vitale` nécessite la présence des librairies dynamiques fournies par le package FSV et la CryptolibCPS. Les instructions d'installation sont disponibles dans le [README](crates/sesam-vitale/README.md) de la crate `sesam-vitale`.
### Lancement
Le logiciel dans sa globalité peut être lancé via la commande suivante :
```bash ```bash
cargo tauri dev cargo tauri dev
``` ```
### Exécution du serveur web `app` en mode endpoint /!\ Attention, le lancement du client `desktop` ne génère pas le fichier `crates/app/assets/css/style.css` automatiquement pour le moment. En cas de modification des interfaces web, il est donc nécessaire de procéder à sa génération comme indiqué dans le [README](crates/app/README.md) de la crate `app`.
Si vous souhaitez lancer les composants séparément, les indications de lancement sont disponibles dans les README des différents crates.
- [app](crates/app/README.md)
- [sesam-vitale](crates/sesam-vitale/README.md)
## Rechargement automatique
Pour permettre de développer plus rapidement, il existe une librairie qui recompile automatiquement nos modifications en cours : [`cargo-watch`](https://github.com/watchexec/cargo-watch) permet de relancer une commande `cargo` lorsqu'un fichier est modifié (example: `cargo run` --> `cargo watch -x run`).
Voici la commande pour l'installer dans un _package_ :
```bash ```bash
cargo run --bin app cargo add cargo-watch --dev --package app
``` ```
Le fichier [`.ignore`](./ignore) permet d'ignorer certains fichiers pour éviter de relancer la recompilation inutilement.
⚠️ La librairie n'est pas compatible avec _Windows 7_ et les versions antérieurs de _Windows_.
## Build ## Build
Packager le client desktop Packager le client desktop

View File

@ -1 +1,4 @@
/target /target
# Tailwind CSS CLI
tailwindcss

View File

@ -7,6 +7,13 @@ edition = "2021"
askama = "0.12.1" askama = "0.12.1"
askama_axum = "0.4.0" askama_axum = "0.4.0"
axum = "0.7.5" axum = "0.7.5"
listenfd = "1.0.1"
notify = "6.1.1"
serde = { version = "1.0.204", features = ["derive"] }
tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5.2", features = ["fs"] } tower-http = { version = "0.5.2", features = ["fs"] }
tower-livereload = "0.9.3"
[dev-dependencies]
cargo-watch = "8.5.1"
systemfd = "0.4.0"

35
crates/app/README.md Normal file
View File

@ -0,0 +1,35 @@
## Pré-requis
- Récupérer le binaire TailwindCSS : https://tailwindcss.com/blog/standalone-cli
## Exécution
- Lancer tailwindcss en mode watch dans un terminal :
```bash
./tailwindcss -i css/input.css -o assets/css/style.css --watch
```
- Lancer le serveur web dans un autre terminal :
```bash
cargo run --bin app
```
## Rechargement automatique (_auto-reload_)
Pour le projet `app`, nous utilisons en plus de `cargo-watch` ses librairies :
- [`systemfd`](https://github.com/mitsuhiko/systemfd) permet de redémarrer un serveur sans interrompre les connexions en cours, il transmet le descripteur de fichier du socket à une nouvelle instance du serveur (exemple: `cargo watch -x run` --> `systemfd --no-pid -s http::3000 -- cargo watch -x run`). Si le port est déjà pris il en prendra un autre.
- [`listenfd`](https://github.com/mitsuhiko/listenfd) permet, côté _Rust_, de démarrer un serveur en utilisant des connexions déjà ouvertes.
Pour notre application voici la commande à lancer :
```bash
systemfd --no-pid -s http::3000 -- cargo watch -x 'run --bin app'
```
## Chargement à chaud (_livereload_)
Pour que notre navigateur rafraîchisse automatique notre page lorsque le serveur a été recompilé, nous utilisons la librairie [`tower-livereload`](https://github.com/leotaku/tower-livereload).
A chaque changement, que ça soit sur du code en _Rust_, _HTML_, _CSS_ ou _JS_ alors le navigateur va recharger entièrement la page.
En Rust, il n'existe pas encore d'outil de _Hot Reload_ complet et intégré comme on en trouve dans d'autres environnements de développement web, comme pour _Node.js_.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
crates/app/css/input.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,27 +1,29 @@
mod pages;
mod templates; mod templates;
use std::path::Path; use std::path::Path;
use askama_axum::IntoResponse; use askama_axum::Template;
use templates::{hello::HelloResponse, index::GetIndexResponse}; use axum::http::{StatusCode, Uri};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
async fn root() -> impl IntoResponse { async fn fallback(uri: Uri) -> (StatusCode, String) {
return GetIndexResponse {}.into_response(); (StatusCode::NOT_FOUND, format!("No route for {uri}"))
} }
async fn hello() -> impl IntoResponse { #[derive(Template)]
return HelloResponse { #[template(path = "index.html")]
name: "Theo".to_string(), pub struct GetIndexTemplate;
}
.into_response(); async fn root() -> GetIndexTemplate {
GetIndexTemplate {}
} }
pub fn get_router(assets_path: &Path) -> axum::Router { pub fn get_router(assets_path: &Path) -> axum::Router {
let router = axum::Router::new() axum::Router::new()
.nest_service("/assets", ServeDir::new(assets_path)) .nest_service("/assets", ServeDir::new(assets_path))
.route("/", axum::routing::get(root)) .route("/", axum::routing::get(root))
.route("/hello", axum::routing::get(hello)); .nest("/pages", pages::get_routes())
.merge(templates::get_routes())
router .fallback(fallback)
} }

View File

@ -1,14 +1,63 @@
use ::app::get_router; use ::app::get_router;
use axum::body::Body;
use axum::http::Request;
use listenfd::ListenFd;
use notify::Watcher;
use std::env; use std::env;
use std::path::Path; use std::path::Path;
use tokio::net::TcpListener;
use tower_livereload::predicate::Predicate;
use tower_livereload::LiveReloadLayer;
/// Nous filtrons les requêtes de `htmx` pour ne pas inclure le script _JS_ qui gère le rechargement
/// Voir https://github.com/leotaku/tower-livereload/pull/3
#[derive(Copy, Clone)]
struct NotHtmxPredicate;
impl<T> Predicate<Request<T>> for NotHtmxPredicate {
fn check(&mut self, req: &Request<T>) -> bool {
!(req.headers().contains_key("hx-request"))
}
}
const DEFAULT_LISTENER: &str = "localhost:3000";
async fn get_tcp_listener() -> TcpListener {
let mut listenfd = ListenFd::from_env();
match listenfd.take_tcp_listener(0).unwrap() {
// if we are given a tcp listener on listen fd 0, we use that one
Some(listener) => {
listener.set_nonblocking(true).unwrap();
TcpListener::from_std(listener).unwrap()
}
// otherwise fall back to local listening
None => TcpListener::bind(DEFAULT_LISTENER).await.unwrap(),
}
}
fn get_livereload_layer(templates_path: &Path) -> LiveReloadLayer<NotHtmxPredicate> {
let livereload = LiveReloadLayer::new();
let reloader = livereload.reloader();
let mut watcher = notify::recommended_watcher(move |_| reloader.reload()).unwrap();
watcher
.watch(templates_path, notify::RecursiveMode::Recursive)
.unwrap();
livereload.request_predicate::<Body, NotHtmxPredicate>(NotHtmxPredicate)
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let assets_path = Path::new(&manifest_dir).join("assets"); let assets_path = Path::new(&manifest_dir).join("assets");
let router = get_router(assets_path.as_path()); let templates_path = Path::new(&manifest_dir).join("templates");
// TODO: select port based on available port (or ask in CLI) let livereload_layer = get_livereload_layer(&templates_path);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); let router = get_router(assets_path.as_path()).layer(livereload_layer);
axum::serve(listener, router).await.unwrap();
let listener: TcpListener = get_tcp_listener().await;
println!("Listening on: http://{}", listener.local_addr().unwrap());
// Run the server with the router
axum::serve(listener, router.into_make_service())
.await
.unwrap();
} }

View File

@ -0,0 +1,9 @@
use askama_axum::Template;
#[derive(Template)]
#[template(path = "pages/cps.html")]
pub struct CpsTemplate;
pub async fn cps() -> CpsTemplate {
CpsTemplate
}

View File

@ -0,0 +1,9 @@
use askama_axum::Template;
#[derive(Template)]
#[template(path = "pages/home.html")]
pub struct HomeTemplate;
pub async fn home() -> HomeTemplate {
HomeTemplate
}

View File

@ -0,0 +1,10 @@
use axum::{routing, Router};
mod cps;
mod home;
pub fn get_routes() -> Router {
Router::new()
.route("/home", routing::get(home::home))
.route("/cps", routing::get(cps::cps))
}

View File

@ -1,7 +1,18 @@
use askama::Template; use askama_axum::Template;
use axum::{routing, Router};
#[derive(Template)] #[derive(Template)]
#[template(path = "hello.html")] #[template(path = "hello.html")]
pub struct HelloResponse { struct HelloTemplate {
pub name: String, pub name: String,
} }
async fn hello() -> HelloTemplate {
HelloTemplate {
name: "Theo".to_string(),
}
}
pub fn get_routes() -> Router {
Router::new().route("/", routing::get(hello))
}

View File

@ -1,5 +0,0 @@
use askama::Template;
#[derive(Template)]
#[template(path = "index.html")]
pub struct GetIndexResponse;

View File

@ -1,2 +1,12 @@
pub mod hello; use axum::Router;
pub mod index;
mod hello;
mod nav;
mod profile;
pub fn get_routes() -> Router {
Router::new()
.nest("/hello", hello::get_routes())
.nest("/nav", nav::get_routes())
.nest("/profile", profile::get_routes())
}

View File

@ -0,0 +1,60 @@
use askama_axum::Template;
use axum::{extract::Query, routing, Router};
use serde::Deserialize;
struct MenuItem {
label: String,
href: String,
current: bool,
}
#[derive(Deserialize)]
struct MenuParameters {
mobile: bool,
}
#[derive(Template)]
#[template(path = "layout/nav/nav-menu-items.html")]
struct MenuTemplate {
mobile: bool,
items: Vec<MenuItem>,
}
impl MenuTemplate {
fn get_classes(&self, is_current_item: &bool) -> String {
let common_classes = match self.mobile {
true => "block border-l-4 py-2 pl-3 pr-4 text-base font-medium".to_string(),
false => {
"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium".to_string()
}
};
match (self.mobile, is_current_item) {
(true, true) => common_classes + " border-indigo-500 bg-indigo-50 text-indigo-700",
(true, false) => common_classes + " border-transparent text-gray-600 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-800",
(false, true) => common_classes + " border-indigo-500 text-gray-900",
(false, false) => common_classes + " border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
}
}
}
async fn menu(Query(params): Query<MenuParameters>) -> MenuTemplate {
MenuTemplate {
mobile: params.mobile,
items: vec![
MenuItem {
label: "Accueil".to_string(),
href: "/pages/home".to_string(),
current: true,
},
MenuItem {
label: "CPS".to_string(),
href: "/pages/cps".to_string(),
current: false,
},
],
}
}
pub fn get_routes() -> Router {
Router::new().route("/menu", routing::get(menu))
}

View File

@ -0,0 +1,63 @@
use askama_axum::Template;
use axum::{extract::Query, routing, Router};
use serde::Deserialize;
struct MenuItem {
label: String,
id: String,
current: bool,
}
#[derive(Deserialize)]
struct MenuParameters {
mobile: bool,
}
#[derive(Template)]
#[template(path = "layout/nav/profile-menu-items.html")]
struct MenuTemplate {
mobile: bool,
items: Vec<MenuItem>,
}
impl MenuTemplate {
fn get_classes(&self, is_current_item: &bool) -> String {
let common_classes = match self.mobile {
true => "block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800".to_string(),
false => "block px-4 py-2 text-sm text-gray-700".to_string(),
};
match (self.mobile, is_current_item) {
(true, true) => common_classes + "", // ???
(true, false) => common_classes + "",
(false, true) => common_classes + " bg-gray-100",
(false, false) => common_classes + "",
}
}
}
async fn menu(Query(params): Query<MenuParameters>) -> MenuTemplate {
MenuTemplate {
mobile: params.mobile,
items: vec![
MenuItem {
label: "Votre profil".to_string(),
id: "profile".to_string(),
current: false,
},
MenuItem {
label: "Paramètres".to_string(),
id: "settings".to_string(),
current: false,
},
MenuItem {
label: "Déconnexion".to_string(),
id: "logout".to_string(),
current: false,
},
],
}
}
pub fn get_routes() -> Router {
Router::new().route("/menu", routing::get(menu))
}

View File

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./templates/**/*.html',
'./css/**/*.css',
],
theme: {
extend: {},
},
plugins: [],
}

View File

@ -1,13 +1,23 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="fr" class="h-full">
<head> <head>
<title>{% block title %}{{ title }}{% endblock %}</title> <title>{% block title %}{{ title }}{% endblock %}</title>
<script src="/assets/js/htmx.min.js"></script> <script src="/assets/js/htmx@2.0.1.min.js"></script>
<script src="/assets/js/alpinejs@3.14.1.min.js" defer></script>
<link href="/assets/css/style.css" rel="stylesheet">
<link href="/assets/css/flowbite@2.5.1.min.css" rel="stylesheet" />
<script src="/assets/js/flowbite@2.5.1.min.js"></script>
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
<body> <body class="h-full">
{% block body %}{% endblock %} <div class="min-h-full">
{% block nav %}
{% include "layout/nav.html" %}
{% endblock %}
{% block body %}{% endblock %}
</div>
</body> </body>
</html> </html>

View File

@ -1,22 +1,33 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Pharma Libre{% endblock %}
{% block body %}
<div> {% block title %}Pharma Libre{% endblock %}
{% block body %}
<div class="py-10">
<header> <header>
<h1>Pharma Libre</h1> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h1
id="page-title"
class="text-3xl font-bold leading-tight tracking-tight text-gray-900"
>
{% include "skeletons/page-title.html" %}
</h1>
</div>
</header> </header>
<main> <main>
<div <div
id="hello" id="main-container"
hx-get="/hello" class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
> >
Loading... <!-- Your content -->
<div
hx-get="/pages/home"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
{% include "skeletons/card.html" %}
</div>
</div> </div>
</main> </main>
</div> </div>

View File

@ -0,0 +1,41 @@
<nav
class="border-b border-gray-200 bg-white"
x-data="{ menuOpen: false }"
>
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 justify-between">
<div class="flex">
{% include "layout/nav/logo.html" %}
<div class="hidden sm:-my-px sm:ml-6 sm:flex sm:space-x-8">
{% include "layout/nav/desktop/menu-items.html" %}
</div>
</div>
<div class="hidden sm:ml-6 sm:flex sm:items-center">
{% include "layout/nav/desktop/notifications-button.html" %}
{% include "layout/nav/desktop/profile.html" %}
</div>
<div class="-mr-2 flex items-center sm:hidden">
{% include "layout/nav/mobile/menu-button.html" %}
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div
class="sm:hidden" id="mobile-menu"
x-show="menuOpen"
x-cloak
>
<div class="space-y-1 pb-3 pt-2">
{% include "layout/nav/mobile/menu-items.html" %}
</div>
<div class="border-t border-gray-200 pb-3 pt-4">
<div class="flex items-center px-4">
{% include "layout/nav/mobile/profile.html" %}
{% include "layout/nav/mobile/notifications-button.html" %}
</div>
<div class="mt-3 space-y-1">
{% include "layout/nav/mobile/profile-items.html" %}
</div>
</div>
</div>
</nav>

View File

@ -0,0 +1,9 @@
<div
id="nav-menu-desktop"
hx-get="/nav/menu?mobile=false"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
{% include "skeletons/menu-items.html" %}
</div>

View File

@ -0,0 +1,6 @@
<button
type="button"
class="relative rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
{% include "layout/nav/notifications-icon.html" %}
</button>

View File

@ -0,0 +1,22 @@
<div
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
role="menu"
id="profile-dropdown"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabindex="-1"
x-show="profileOpen"
x-on:click.outside="profileOpen = false"
x-cloak
x-transition
>
<div
id="profile-menu-desktop"
hx-get="/profile/menu?mobile=false"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>
</div>

View File

@ -0,0 +1,27 @@
<!-- Profile dropdown -->
<div
class="relative ml-3"
x-data="{ profileOpen: false }"
>
<div>
<button
type="button"
class="relative flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
id="user-menu-button"
aria-controls="profile-dropdown"
aria-expanded="false"
aria-haspopup="menu"
x-on:click="profileOpen = ! profileOpen"
>
<span class="absolute -inset-1.5"></span>
<span class="sr-only">Open user menu</span>
<img
class="h-8 w-8 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</button>
</div>
{% include "layout/nav/desktop/profile-dropdown.html" %}
</div>

View File

@ -0,0 +1,4 @@
<div class="flex flex-shrink-0 items-center">
<img class="block h-8 w-auto lg:hidden" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company">
<img class="hidden h-8 w-auto lg:block" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company">
</div>

View File

@ -0,0 +1,43 @@
<!-- Mobile menu button -->
<button
type="button"
class="relative inline-flex items-center justify-center rounded-md bg-white p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
aria-controls="mobile-menu"
x-on:click="menuOpen = ! menuOpen"
x-bind:aria-expanded="menuOpen"
>
<span class="absolute -inset-0.5"></span>
<span class="sr-only">Open main menu</span>
<!-- Menu open: "hidden", Menu closed: "block" -->
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
x-bind:class="menuOpen ? 'hidden' : 'block'"
x-bind:aria-hidden="menuOpen"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
<!-- Menu open: "block", Menu closed: "hidden" -->
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
x-bind:class="menuOpen ? 'block' : 'hidden'"
x-bind:aria-hidden="! menuOpen"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>

View File

@ -0,0 +1,9 @@
<div
id="nav-menu-mobile"
hx-get="/nav/menu?mobile=true"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>

View File

@ -0,0 +1,6 @@
<button
type="button"
class="relative ml-auto flex-shrink-0 rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
{% include "layout/nav/notifications-icon.html" %}
</button>

View File

@ -0,0 +1,9 @@
<div
id="profile-menu-mobile"
hx-get="/profile/menu?mobile=true"
hx-target="this"
hx-trigger="load"
hx-swap="outerHTML"
>
Chargement ...
</div>

View File

@ -0,0 +1,11 @@
<div class="flex-shrink-0">
<img
class="h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div class="ml-3">
<div class="text-base font-medium text-gray-800">Tom Cook</div>
<div class="text-sm font-medium text-gray-500">tom@example.com</div>
</div>

View File

@ -0,0 +1,13 @@
{% for item in items %}
<a
href=""
hx-get="{{ item.href }}"
hx-trigger="click"
hx-target="#main-container"
hx-swap="innerHTML"
class="{{ Self::get_classes(self, item.current) }}"
aria-current="{% if item.current %}page{% endif %}"
>
{{ item.label }}
</a>
{% endfor %}

View File

@ -0,0 +1,16 @@
<span class="absolute -inset-1.5"></span>
<span class="sr-only">View notifications</span>
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"
/>
</svg>

View File

@ -0,0 +1,11 @@
{% for item in items %}
<a
href="#{{ item.id }}"
class="{{ Self::get_classes(self, item.current) }}"
role="menuitem"
tabindex="-1"
id="{{ item.id }}"
>
{{ item.label }}
</a>
{% endfor %}

View File

@ -0,0 +1,52 @@
<h3 id="page-title" hx-swap-oob="textContent">
CPS
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>

View File

@ -0,0 +1,52 @@
<h3 id="page-title" hx-swap-oob="textContent">
Accueil
</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
></div>
<div class="grid grid-cols-2 gap-4">
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-48 md:h-72"
></div>
</div>

View File

@ -0,0 +1,22 @@
<div role="status" class="animate-pulse max-w-sm p-4 border border-gray-200 rounded shadow md:p-6 dark:border-gray-700">
<div class="flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700">
<svg class="w-10 h-10 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 20">
<path d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z"/>
<path d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"/>
</svg>
</div>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
<div class="flex items-center mt-4">
<svg class="w-10 h-10 me-3 text-gray-200 dark:text-gray-700" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/>
</svg>
<div>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-32 mb-2"></div>
<div class="w-48 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
</div>
</div>
<span class="sr-only">Loading...</span>
</div>

View File

@ -0,0 +1,4 @@
<div role="status" class="animate-pulse flex items-center justify-center h-full">
<div class="w-32 h-4 bg-gray-200 rounded-full dark:bg-gray-700 me-3"></div>
<div class="w-32 h-4 bg-gray-200 rounded-full dark:bg-gray-700"></div>
</div>

View File

@ -0,0 +1 @@
<div role="status" class="animate-pulse h-7 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mt-3"></div>

View File

@ -0,0 +1,3 @@
SESAM_FSV_VERSION=1.40.14
SESAM_FSV_LIB_PATH="C:/Program Files/santesocial/fsv/${SESAM_FSV_VERSION}/lib"
SESAM_FSV_SSVLIB=ssvw64

View File

@ -0,0 +1,2 @@
SESAM_FSV_VERSION=1.40.14
SESAM_INI_PATH=${ALLUSERSPROFILE}\\santesocial\\fsv\\${SESAM_FSV_VERSION}\\conf\\sesam.ini

View File

@ -1,2 +1,2 @@
SESAM_FSV_VERSION=1.40.13 SESAM_FSV_VERSION=1.40.13
SESAM_INI_PATH=${ALLUSERSPROFILE}\\santesocial\\fsv\\${SESAM_FSV_VERSION}\\conf\\sesam.ini SESAM_INI_PATH=${ALLUSERSPROFILE}\\santesocial\\fsv\\${SESAM_FSV_VERSION}\\conf\\sesam.ini

View File

@ -16,7 +16,10 @@ fn main() {
// Add local lib directory to the linker search path (for def files and static libs) // Add local lib directory to the linker search path (for def files and static libs)
let static_lib_path = manifest_path.join("lib"); let static_lib_path = manifest_path.join("lib");
println!("cargo::rustc-link-search=native={}", static_lib_path.display()); println!(
"cargo::rustc-link-search=native={}",
static_lib_path.display()
);
// Add the SESAM_FSV_LIB_PATH to the linker search path // Add the SESAM_FSV_LIB_PATH to the linker search path
let fsv_lib_path = PathBuf::from(env::var("SESAM_FSV_LIB_PATH").unwrap()); let fsv_lib_path = PathBuf::from(env::var("SESAM_FSV_LIB_PATH").unwrap());
@ -31,6 +34,9 @@ fn main() {
} }
// Link the SESAM_FSV_SSVLIB dynamic library // Link the SESAM_FSV_SSVLIB dynamic library
println!("cargo::rustc-link-lib=dylib={}", env::var("SESAM_FSV_SSVLIB").unwrap()); println!(
"cargo::rustc-link-lib=dylib={}",
env::var("SESAM_FSV_SSVLIB").unwrap()
);
// TODO : try `raw-dylib` instead of `dylib` on Windows to avoid the need of the `lib` headers compiled from the `def` // TODO : try `raw-dylib` instead of `dylib` on Windows to avoid the need of the `lib` headers compiled from the `def`
} }

Binary file not shown.

View File

@ -0,0 +1,876 @@
use libc::{c_void, size_t};
use std::ffi::CString;
use std::ptr;
use crate::libssv::SSV_LireDroitsVitale;
use crate::ssv_memory::{decode_ssv_memory, Block};
#[derive(Debug, Default)]
pub struct CarteVitale {
donneesAssure: DonneesAssure,
serviceAMOFamille: ServiceAMOFamille,
donneesAccidentTravail: DonneesAccidentTravail,
donneesBeneficiaire: Vec<DonneesBeneficiaire>,
}
// 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 DonneesAssure {
type_de_carte_vitale: char, // CA
numero_de_serie: String, // CN - 1 -> 20
date_de_fin_de_validite: String, // CN - Format AAAAMMJJ0000
donnees_administration_ruf1: char, // CN
donnees_administration_ruf2: String, // CA - 24
donnees_ruf_administration: String, // CE - 10
type_d_identification_du_porteur: char, // CA
numero_national_d_immatriculation: String, // CA - 13
cle_du_nir: String, // CA - 2
code_regime: String, // CN - 2
caisse_gestionnaire: String, // CN - 3
centre_gestionnaire: String, // CN - 4
code_gestion: String, // CA - 2
donnees_ruf_famille: String, // CE - 55
}
#[derive(Debug, Default)]
struct ServiceAMOFamille {
code_service_amo_famille: String, // CN - 2
date_de_debut_service_amo_famille: String, // CN - Format AAAAMMJJ0000
date_de_fin_service_amo_famille: String, // CN - Format AAAAMMJJ0000
}
#[derive(Debug, Default)]
struct DonneesAccidentTravail {
organisme_gestionnaire_risque_at: String, // CN - 9
code_at: char, // CA - 2
identifiant_at: String, // CN - 9
organisme_gestionnaire_at1: String, // CN - 9
code_at1: char, // CA - 2
identifiant_at1: String, // CN - 9
organisme_gestionnaire_at2: String, // CN - 9
code_at2: char, // CA - 2
identifiant_at2: String, // CN - 9
}
#[derive(Debug, Default)]
struct DonneesBeneficiaire {
nom_usuel: String, // CE - 27
nom_de_famille: String, // CE - 27
prenom: String, // CE - 27
adresse_ligne_1: String, // CE - 32
adresse_ligne_2: String, // CE - 32
adresse_ligne_3: String, // CE - 32
adresse_ligne_4: String, // CE - 32
adresse_ligne_5: String, // CE - 32
nir_certifie: String, // CA - 13
cle_du_nir_certifie: String, // CN - 2
date_de_certification_du_nir: String, // CN - Format AAAAMMJJ0000
date_de_naissance: String, // CN - Format AAAAMMJJ0000
rang_de_naissance: String, // CN - 1
qualite: String, // CN - 2
code_service_amo_beneficiaire: char, // CA - 2
date_de_debut_service_amo: String, // CN - Format AAAAMMJJ0000
date_de_fin_service_amo: String, // CN - Format AAAAMMJJ0000
donnees_ruf_amo: String, // CE - 30
periodeDroitsAMO: Vec<PeriodeDroitsAMO>,
periodeCodeCouverture: Vec<PeriodeCodeCouverture>,
donneesMutuelle: DonneesMutuelle,
donneesComplementaire: DonneesComplementaire,
donneesRUFBeneficiaireComplementaire: DonneesRUFBeneficiaireComplementaire,
}
#[derive(Debug, Default)]
struct PeriodeDroitsAMO {
date_de_debut_droits_amo: String, // CN - Format AAAAMMJJ0000
date_de_fin_droits_amo: String, // CN - Format AAAAMMJJ0000
}
#[derive(Debug, Default)]
struct PeriodeCodeCouverture {
date_de_debut_code_couverture: String, // CN - Format AAAAMMJJ0000
date_de_fin_code_couverture: String, // CN - Format AAAAMMJJ0000
code_ald: String, // CN - 1
code_situation: String, // CN - 4
}
#[derive(Debug, Default)]
struct DonneesMutuelle {
identification_mutuelle: String, // CN - 8
garanties_effectives: String, // CA - 8
indicateur_de_traitement_mutuelle: String, // CN - 2
type_de_services_associes: char, // CA - 1
services_associes_au_contrat: String, // CA - 17
code_aiguillage_sts: char, // CA - 1
periodeDroitsMutuelle: Vec<PeriodeDroitsMutuelle>,
}
#[derive(Debug, Default)]
struct PeriodeDroitsMutuelle {
date_de_debut_droits_mutuelle: String, // CN - Format AAAAMMJJ0000
date_de_fin_droits_mutuelle: String, // CN - Format AAAAMMJJ0000
}
#[derive(Debug, Default)]
struct DonneesComplementaire {
numero_complementaire_b2: String, // CA - 10
numero_complementaire_edi: String, // CA - 19
numero_adherent_amc: String, // CA - 8
indicateur_de_traitement_amc: String, // CN - 2
date_de_debut_validite_amc: String, // CN - Format AAAAMMJJ0000
date_de_fin_validite_amc: String, // CN - Format AAAAMMJJ0000
code_routage_flux_amc: String, // CA - 2
identifiant_hote: String, // CA - 3
nom_de_domaine_amc: String, // CE - 20
code_aiguillage_sts: char, // CA - 1
type_de_services_associes: char, // CA - 1
services_associes_au_contrat: String, // CE - 17
}
#[derive(Debug, Default)]
struct DonneesRUFBeneficiaireComplementaire {
donnees_ruf_beneficiaire_complementaire: String, // CE - 115
}
pub fn LireDroitsVitale(
lecteurPS: &str,
lecteurVitale: &str,
codePorteurPS: &str,
dateConsultation: &str,
) -> Result<CarteVitale, String> {
let resource_ps = CString::new(lecteurPS).expect("CString::new failed");
let resource_vitale = CString::new(lecteurVitale).expect("CString::new failed");
let card_number = CString::new(codePorteurPS).expect("CString::new failed");
let date_consultation = CString::new(dateConsultation).expect("CString::new failed");
let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0;
let mut hex_values: &[u8] = &[];
unsafe {
let result = SSV_LireDroitsVitale(
resource_ps.as_ptr(),
resource_vitale.as_ptr(),
card_number.as_ptr(),
date_consultation.as_ptr(),
&mut buffer,
&mut size,
);
println!("SSV_LireDroitsVitale result: {}", result);
if !buffer.is_null() {
hex_values = std::slice::from_raw_parts(buffer as *const u8, size);
}
}
let groups = decode_ssv_memory(hex_values, hex_values.len());
let OUT = decode_carte_vitale(groups);
unsafe {
if !buffer.is_null() {
libc::free(buffer); // ??? si liberer avant decode_ssv_memory et decode_carte_vitale alors hex_values n'est plus correct ???
}
}
OUT
}
fn decode_carte_vitale(groups: Vec<Block>) -> Result<CarteVitale, String> {
let mut cartevitale = CarteVitale::default();
for group in groups {
for field in group.content {
match (group.id, field.id) {
(104, 1) => {
cartevitale
.donneesBeneficiaire
.push(DonneesBeneficiaire::default());
}
(105, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeDroitsAMO
.push(PeriodeDroitsAMO::default());
}
(106, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeCodeCouverture
.push(PeriodeCodeCouverture::default());
}
(108, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.periodeDroitsMutuelle
.push(PeriodeDroitsMutuelle::default());
}
_ => {}
}
if field.content.len() > 0 {
match (group.id, field.id) {
(101, 1) => {
let byte = field.content[0];
cartevitale.donneesAssure.type_de_carte_vitale = byte as char;
}
(101, 2) => {
cartevitale.donneesAssure.numero_de_serie = //bytes_to_decimal_string(&field.content);
String::from_utf8_lossy(field.content).to_string();
}
(101, 3) => {
cartevitale.donneesAssure.date_de_fin_de_validite =
String::from_utf8_lossy(field.content).to_string();
}
(101, 4) => {
println!("101.4 field.size: {:#?}", field.size);
let byte = field.content[0];
cartevitale.donneesAssure.donnees_administration_ruf1 = byte as char;
}
(101, 5) => {
cartevitale.donneesAssure.donnees_administration_ruf2 =
String::from_utf8_lossy(field.content).to_string();
}
(101, 6) => {
cartevitale.donneesAssure.donnees_ruf_administration =
String::from_utf8_lossy(field.content).to_string();
}
(101, 7) => {
let byte = field.content[0];
cartevitale.donneesAssure.type_d_identification_du_porteur = byte as char;
}
(101, 8) => {
cartevitale.donneesAssure.numero_national_d_immatriculation =
String::from_utf8_lossy(field.content).to_string();
}
(101, 9) => {
cartevitale.donneesAssure.cle_du_nir =
String::from_utf8_lossy(field.content).to_string();
}
(101, 10) => {
cartevitale.donneesAssure.code_regime =
String::from_utf8_lossy(field.content).to_string();
}
(101, 11) => {
cartevitale.donneesAssure.caisse_gestionnaire =
String::from_utf8_lossy(field.content).to_string();
}
(101, 12) => {
cartevitale.donneesAssure.centre_gestionnaire =
String::from_utf8_lossy(field.content).to_string();
}
(101, 13) => {
cartevitale.donneesAssure.code_gestion =
String::from_utf8_lossy(field.content).to_string();
}
(101, 14) => {
cartevitale.donneesAssure.donnees_ruf_famille =
String::from_utf8_lossy(field.content).to_string();
}
(102, 1) => {
cartevitale.serviceAMOFamille.code_service_amo_famille =
String::from_utf8_lossy(field.content).to_string();
}
(102, 2) => {
cartevitale
.serviceAMOFamille
.date_de_debut_service_amo_famille =
String::from_utf8_lossy(field.content).to_string();
}
(102, 3) => {
cartevitale
.serviceAMOFamille
.date_de_fin_service_amo_famille =
String::from_utf8_lossy(field.content).to_string();
}
(103, 1) => {
cartevitale
.donneesAccidentTravail
.organisme_gestionnaire_risque_at =
String::from_utf8_lossy(field.content).to_string();
}
(103, 2) => {
let byte = field.content[0];
cartevitale.donneesAccidentTravail.code_at = byte as char;
}
(103, 3) => {
cartevitale.donneesAccidentTravail.identifiant_at =
String::from_utf8_lossy(field.content).to_string();
}
(103, 4) => {
cartevitale
.donneesAccidentTravail
.organisme_gestionnaire_at1 =
String::from_utf8_lossy(field.content).to_string();
}
(103, 5) => {
let byte = field.content[0];
cartevitale.donneesAccidentTravail.code_at1 = byte as char;
}
(103, 6) => {
cartevitale.donneesAccidentTravail.identifiant_at1 =
String::from_utf8_lossy(field.content).to_string();
}
(103, 7) => {
cartevitale
.donneesAccidentTravail
.organisme_gestionnaire_at2 =
String::from_utf8_lossy(field.content).to_string();
}
(103, 8) => {
let byte = field.content[0];
cartevitale.donneesAccidentTravail.code_at2 = byte as char;
}
(103, 9) => {
cartevitale.donneesAccidentTravail.identifiant_at2 =
String::from_utf8_lossy(field.content).to_string();
}
(104, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.nom_usuel = String::from_utf8_lossy(field.content).to_string();
}
(104, 2) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.nom_de_famille = String::from_utf8_lossy(field.content).to_string();
}
(104, 3) => {
cartevitale.donneesBeneficiaire.last_mut().unwrap().prenom =
String::from_utf8_lossy(field.content).to_string();
}
(104, 4) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.adresse_ligne_1 = String::from_utf8_lossy(field.content).to_string();
}
(104, 5) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.adresse_ligne_2 = String::from_utf8_lossy(field.content).to_string();
}
(104, 6) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.adresse_ligne_3 = String::from_utf8_lossy(field.content).to_string();
}
(104, 7) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.adresse_ligne_4 = String::from_utf8_lossy(field.content).to_string();
}
(104, 8) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.adresse_ligne_5 = String::from_utf8_lossy(field.content).to_string();
}
(104, 9) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.nir_certifie = String::from_utf8_lossy(field.content).to_string();
}
(104, 10) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.cle_du_nir_certifie =
String::from_utf8_lossy(field.content).to_string();
}
(104, 11) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.date_de_certification_du_nir =
String::from_utf8_lossy(field.content).to_string();
}
(104, 12) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.date_de_naissance = String::from_utf8_lossy(field.content).to_string();
}
(104, 13) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.rang_de_naissance = String::from_utf8_lossy(field.content).to_string();
}
(104, 14) => {
cartevitale.donneesBeneficiaire.last_mut().unwrap().qualite =
String::from_utf8_lossy(field.content).to_string();
}
(104, 15) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.code_service_amo_beneficiaire = field.content[0] as char;
}
(104, 16) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.date_de_debut_service_amo =
String::from_utf8_lossy(field.content).to_string();
}
(104, 17) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.date_de_fin_service_amo =
String::from_utf8_lossy(field.content).to_string();
}
(104, 18) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donnees_ruf_amo = String::from_utf8_lossy(field.content).to_string();
}
(105, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeDroitsAMO
.last_mut()
.unwrap()
.date_de_debut_droits_amo =
String::from_utf8_lossy(field.content).to_string();
}
(105, 2) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeDroitsAMO
.last_mut()
.unwrap()
.date_de_fin_droits_amo =
String::from_utf8_lossy(field.content).to_string();
}
(106, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeCodeCouverture
.last_mut()
.unwrap()
.date_de_debut_code_couverture =
String::from_utf8_lossy(field.content).to_string();
}
(106, 2) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeCodeCouverture
.last_mut()
.unwrap()
.date_de_fin_code_couverture =
String::from_utf8_lossy(field.content).to_string();
}
(106, 3) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeCodeCouverture
.last_mut()
.unwrap()
.code_ald = String::from_utf8_lossy(field.content).to_string();
}
(106, 4) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.periodeCodeCouverture
.last_mut()
.unwrap()
.code_situation = String::from_utf8_lossy(field.content).to_string();
}
(107, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.identification_mutuelle =
String::from_utf8_lossy(field.content).to_string();
}
(107, 2) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.garanties_effectives =
String::from_utf8_lossy(field.content).to_string();
}
(107, 3) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.indicateur_de_traitement_mutuelle =
String::from_utf8_lossy(field.content).to_string();
}
(107, 4) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.type_de_services_associes = field.content[0] as char;
}
(107, 5) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.services_associes_au_contrat =
String::from_utf8_lossy(field.content).to_string();
}
(107, 6) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.code_aiguillage_sts = field.content[0] as char;
}
(108, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.periodeDroitsMutuelle
.last_mut()
.unwrap()
.date_de_debut_droits_mutuelle =
String::from_utf8_lossy(field.content).to_string();
}
(108, 2) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesMutuelle
.periodeDroitsMutuelle
.last_mut()
.unwrap()
.date_de_fin_droits_mutuelle =
String::from_utf8_lossy(field.content).to_string();
}
(109, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.numero_complementaire_b2 =
String::from_utf8_lossy(field.content).to_string();
}
(109, 2) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.numero_complementaire_edi =
String::from_utf8_lossy(field.content).to_string();
}
(109, 3) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.numero_adherent_amc =
String::from_utf8_lossy(field.content).to_string();
}
(109, 4) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.indicateur_de_traitement_amc =
String::from_utf8_lossy(field.content).to_string();
}
(109, 5) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.date_de_debut_validite_amc =
String::from_utf8_lossy(field.content).to_string();
}
(109, 6) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.date_de_fin_validite_amc =
String::from_utf8_lossy(field.content).to_string();
}
(109, 7) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.code_routage_flux_amc =
String::from_utf8_lossy(field.content).to_string();
}
(109, 8) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.identifiant_hote = String::from_utf8_lossy(field.content).to_string();
}
(109, 9) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.nom_de_domaine_amc =
String::from_utf8_lossy(field.content).to_string();
}
(109, 10) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.code_aiguillage_sts = field.content[0] as char;
}
(109, 11) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.type_de_services_associes = field.content[0] as char;
}
(109, 12) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesComplementaire
.services_associes_au_contrat =
String::from_utf8_lossy(field.content).to_string();
}
(111, 1) => {
cartevitale
.donneesBeneficiaire
.last_mut()
.unwrap()
.donneesRUFBeneficiaireComplementaire
.donnees_ruf_beneficiaire_complementaire =
String::from_utf8_lossy(field.content).to_string();
}
_ => {
return Err(format!(
"Unknown (group, field) pair: ({}, {})",
group.id, field.id
))
}
}
}
}
}
Ok(cartevitale)
}
// #[cfg(test)]
// mod test_decode_carte_ps {
// use super::*;
// #[test]
// fn test_francoise_pharmacien0052419() {
// let bytes: &[u8] = &[
// 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).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());
// 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

@ -0,0 +1,408 @@
use libc::{c_void, size_t};
use std::ffi::CString;
use std::ptr;
use crate::libssv::SSV_LireCartePS;
use crate::ssv_memory::{decode_ssv_memory, Block};
#[derive(Debug, Default)]
pub struct CartePS {
titulaire: TitulairePS,
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
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, 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");
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 !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());
decode_carte_ps(groups)
}
fn decode_carte_ps(groups: Vec<Block>) -> Result<CartePS, String> {
let mut carte_ps = CartePS::default();
for group in groups {
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());
carte_ps
.situations
.last_mut()
.unwrap()
.numero_logique_de_la_situation_de_facturation_du_ps = field.content[0];
}
(2..=16, 2) => {
carte_ps.situations.last_mut().unwrap().mode_d_exercice =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 3) => {
carte_ps.situations.last_mut().unwrap().statut_d_exercice =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 4) => {
carte_ps.situations.last_mut().unwrap().secteur_d_activite =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 5) => {
carte_ps
.situations
.last_mut()
.unwrap()
.type_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 6) => {
carte_ps
.situations
.last_mut()
.unwrap()
.numero_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 7) => {
carte_ps
.situations
.last_mut()
.unwrap()
.cle_du_numero_d_identification_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 8) => {
carte_ps
.situations
.last_mut()
.unwrap()
.raison_sociale_structure =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 9) => {
carte_ps
.situations
.last_mut()
.unwrap()
.numero_d_identification_de_facturation_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 10) => {
carte_ps
.situations
.last_mut()
.unwrap()
.cle_du_numero_d_identification_de_facturation_du_ps =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 11) => {
carte_ps
.situations
.last_mut()
.unwrap()
.numero_d_identification_du_ps_remplaçant =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 12) => {
carte_ps
.situations
.last_mut()
.unwrap()
.cle_du_numero_d_identification_du_ps_remplaçant =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 13) => {
carte_ps.situations.last_mut().unwrap().code_conventionnel =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 14) => {
carte_ps.situations.last_mut().unwrap().code_specialite =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 15) => {
carte_ps.situations.last_mut().unwrap().code_zone_tarifaire =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 16) => {
carte_ps.situations.last_mut().unwrap().code_zone_ik =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 17) => {
carte_ps.situations.last_mut().unwrap().code_agrement_1 =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 18) => {
carte_ps.situations.last_mut().unwrap().code_agrement_2 =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 19) => {
carte_ps.situations.last_mut().unwrap().code_agrement_3 =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 20) => {
carte_ps
.situations
.last_mut()
.unwrap()
.habilitation_à_signer_une_facture =
String::from_utf8_lossy(field.content).to_string();
}
(2..=16, 21) => {
carte_ps
.situations
.last_mut()
.unwrap()
.habilitation_à_signer_un_lot =
String::from_utf8_lossy(field.content).to_string();
}
_ => {
return Err(format!(
"Unknown (group, field) pair: ({}, {})",
group.id, field.id
))
}
}
}
}
Ok(carte_ps)
}
#[cfg(test)]
mod test_decode_carte_ps {
use super::*;
#[test]
fn test_francoise_pharmacien0052419() {
let bytes: &[u8] = &[
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).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());
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,3 +1,9 @@
pub mod cps;
pub mod cartevitale;
pub mod libssv;
pub mod ssv_memory;
pub mod ssvlib_demo;
pub fn add(left: usize, right: usize) -> usize { pub fn add(left: usize, right: usize) -> usize {
left + right left + right
} }

View File

@ -1,17 +1,31 @@
/** /// libssv.rs
* libssv.rs ///
* /// Low level bindings to the SSVLIB dynamic library.
* Low level bindings to the SSVLIB dynamic library. // TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate
* TODO : look for creating a dedicated *-sys crate : https://kornel.ski/rust-sys-crate use libc::{c_char, c_ushort, c_void, size_t};
*/
use libc::{ c_char, c_void, c_ushort, size_t };
#[cfg_attr(target_os = "linux", link(name = "ssvlux64"))] #[cfg_attr(target_os = "linux", link(name = "ssvlux64"))]
#[cfg_attr(target_os = "windows", link(name = "ssvw64"))] #[cfg_attr(target_os = "windows", link(name = "ssvw64"))]
extern "C" { extern "C" {
pub fn SSV_InitLIB2(pcRepSesamIni: *const c_char) -> c_ushort; pub fn SSV_InitLIB2(pcRepSesamIni: *const c_char) -> c_ushort;
pub fn SSV_LireCartePS(NomRessourcePS: *const c_char, NomRessourceLecteur: *const c_char, CodePorteurPS: *const c_char, ZDonneesSortie: *mut *mut c_void, TTailleDonneesSortie: *mut size_t) -> c_ushort; pub fn SSV_LireCartePS(
pub fn SSV_LireConfig(ZDonneesSortie: *mut *mut c_void, TTailleDonneesSortie: *mut size_t) -> c_ushort; NomRessourcePS: *const c_char,
NomRessourceLecteur: *const c_char,
CodePorteurPS: *const c_char,
ZDonneesSortie: *mut *mut c_void,
TTailleDonneesSortie: *mut size_t,
) -> c_ushort;
pub fn SSV_LireConfig(
ZDonneesSortie: *mut *mut c_void,
TTailleDonneesSortie: *mut size_t,
) -> c_ushort;
pub fn SSV_LireDroitsVitale(
NomRessourcePS: *const c_char,
NomRessourceLecteur: *const c_char,
CodePorteurPS: *const c_char,
DateConsultation: *const c_char,
ZDonneesSortie: *mut *mut c_void,
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

@ -1,5 +1,8 @@
mod ssvlib_demo; mod cps;
mod cartevitale;
mod libssv; mod libssv;
mod ssv_memory;
mod ssvlib_demo;
fn main() { fn main() {
ssvlib_demo::demo(); ssvlib_demo::demo();

View File

@ -0,0 +1,285 @@
/// # SSV Memory
/// Provide functions to manipulate raw memory from SSV library.
use std::convert::TryFrom;
#[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> {
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;
} else {
// Size coded on N bytes
// N are the 7 lower bits of the first byte
let size_bytes_len = (bytes[0] & 0b0111_1111) as usize;
if size_bytes_len > bytes.len() - 1 {
return Err("Invalid memory: not enough bytes to read the size");
} else if size_bytes_len > 4 {
return Err("Invalid memory: size is too big");
}
let size_bytes = &bytes[1..1 + size_bytes_len];
// u32::from_be_bytes() requires a 4 bytes array
let mut padded_bytes = [0u8; 4];
padded_bytes[size_bytes_len..].copy_from_slice(size_bytes);
element_size.size = u32::from_be_bytes(padded_bytes) as usize;
element_size.pad += size_bytes_len;
}
Ok(element_size)
}
}
#[derive(Debug)]
pub struct Block<'a> {
pub id: u16,
pub size: usize,
pub content: Vec<Field<'a>>,
}
impl<'a> From<&'a [u8]> for Block<'a> {
fn from(bytes: &'a [u8]) -> Self {
let mut offset = 0;
let id = u16::from_be_bytes(bytes[..2].try_into().unwrap());
offset += 2;
let ElementSize {
size: block_size,
pad,
} = bytes[2..].try_into().unwrap();
offset += pad;
let raw_content = &bytes[offset..];
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 mut field: Field<'a> = raw_content[field_offset..].into();
field.id = field_id;
field_offset += field.size;
field_id += 1;
content.push(field);
}
Block {
id,
size: offset + block_size,
content,
}
}
}
#[derive(Debug)]
pub struct Field<'a> {
pub id: u16,
pub size: usize,
pub content: &'a [u8],
}
impl<'a> From<&'a [u8]> for Field<'a> {
fn from(bytes: &'a [u8]) -> Self {
let ElementSize { size, pad } = bytes.try_into().unwrap();
let contenu = &bytes[pad..pad + size];
Field {
id: 0,
size: pad + size,
content: contenu,
}
}
}
pub fn decode_ssv_memory(bytes: &[u8], size: usize) -> Vec<Block> {
let mut blocks: Vec<Block> = Vec::new();
let mut offset = 0;
while offset < size {
let block: Block = bytes[offset..].into();
offset += block.size;
blocks.push(block);
}
blocks
}
#[cfg(test)]
mod test_element_size {
use super::*;
#[test]
fn short_size() {
let bytes: &[u8] = &[0b_0000_0001_u8];
let element_size: ElementSize = bytes.try_into().unwrap();
assert_eq!(element_size.size, 1);
assert_eq!(element_size.pad, 1);
let bytes: &[u8] = &[0b_0100_0000_u8];
let element_size: ElementSize = bytes.try_into().unwrap();
assert_eq!(element_size.size, 64);
assert_eq!(element_size.pad, 1);
}
#[test]
fn long_size() {
let bytes: &[u8] = &[0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0100_0000_u8];
let element_size: ElementSize = bytes.try_into().unwrap();
assert_eq!(element_size.size, 320);
assert_eq!(element_size.pad, 3);
}
#[test]
fn null_size() {
let bytes: &[u8] = &[];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(result, Err("Empty bytes input"),);
}
#[test]
fn invalid_memory() {
let bytes: &[u8] = &[0b_1000_0001_u8];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(
result,
Err("Invalid memory: not enough bytes to read the size"),
);
let bytes: &[u8] = &[0b_1000_0010_u8, 1];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(
result,
Err("Invalid memory: not enough bytes to read the size"),
);
let bytes: &[u8] = &[0b_1000_0101_u8, 1, 1, 1, 1, 1];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(result, Err("Invalid memory: size is too big"),);
}
}
#[cfg(test)]
mod test_field {
use super::*;
#[test]
fn short_size() {
let bytes: &[u8] = &[
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,
];
let element: Field = bytes.into();
assert_eq!(element.size, 52);
assert_eq!(element.content[..5], [1, 48, 1, 56, 11]);
}
#[test]
fn long_size() {
let mut bytes_vec = vec![
0b_1000_0010_u8,
0b_0000_0001_u8,
0b_0000_0000_u8, // size = 256
];
// Add 256 bytes to the content
bytes_vec.append(&mut vec![1; 256]);
let bytes: &[u8] = &bytes_vec;
let element: Field = bytes.into();
assert_eq!(element.size, 259);
assert_eq!(element.content.len(), 256);
}
}
#[cfg(test)]
mod test_block {
use super::*;
#[test]
fn test_francoise_pharmacien0052419_partial_block_1() {
let bytes: &[u8] = &[1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52];
let field1: Field = bytes.into();
assert_eq!(field1.size, 2);
assert_eq!(field1.content, &[48]);
let field2: Field = bytes[field1.size..].into();
assert_eq!(field2.size, 2);
assert_eq!(field2.content, &[56]);
let field3: Field = bytes[field1.size + field2.size..].into();
assert_eq!(field3.size, 12);
assert_eq!(
field3.content,
&[57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52]
);
}
#[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,
];
let first_block: Block = bytes.into();
assert_eq!(first_block.id, 1);
assert_eq!(first_block.size, 54);
assert_eq!(first_block.content.len(), 8);
let second_block: Block = bytes[first_block.size..].into();
assert_eq!(second_block.id, 2);
assert_eq!(second_block.size, 86);
assert_eq!(second_block.content.len(), 21);
}
}
#[cfg(test)]
mod test_decode_ssv_memory {
use super::*;
#[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,
];
let blocks = decode_ssv_memory(bytes, bytes.len());
assert_eq!(blocks.len(), 2);
}
}

View File

@ -1,24 +1,17 @@
/** /// High level API for the SSV library,
* High level API for the SSV library, /// based on the low level bindings in libssv.rs.
* based on the low level bindings in libssv.rs.
*
*/
extern crate libc;
extern crate dotenv; extern crate dotenv;
use libc::{c_void, size_t};
use libc::{ c_void, size_t }; use std::env;
use std::ffi::CString; use std::ffi::CString;
use std::path::PathBuf; use std::path::PathBuf;
use std::ptr; use std::ptr;
use std::env;
use crate::libssv:: { use crate::cps::lire_carte;
SSV_InitLIB2, use crate::cartevitale::LireDroitsVitale;
SSV_LireCartePS,
SSV_LireConfig use crate::libssv::{SSV_InitLIB2, SSV_LireConfig};
};
fn ssv_init_lib_2() { fn ssv_init_lib_2() {
let ini_str = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); let ini_str = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set");
@ -29,35 +22,6 @@ fn ssv_init_lib_2() {
} }
} }
fn ssv_lire_carte_ps() {
let resource_ps = CString::new("PS").expect("CString::new failed");
let resource_reader = CString::new("TRANSPA1").expect("CString::new failed");
let card_number = CString::new("1234567890").expect("CString::new failed");
let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0;
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 !buffer.is_null() {
let hex_values = std::slice::from_raw_parts(buffer as *const u8, size);
for &byte in hex_values {
print!("{:02X} ", byte);
}
println!();
libc::free(buffer);
}
}
}
fn ssv_lire_config() { fn ssv_lire_config() {
let mut buffer: *mut c_void = ptr::null_mut(); let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0; let mut size: size_t = 0;
@ -78,10 +42,8 @@ fn ssv_lire_config() {
} }
pub fn demo() { 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`
* 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
* 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_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let manifest_path = PathBuf::from(manifest_dir); let manifest_path = PathBuf::from(manifest_dir);
dotenv::from_path(manifest_path.join(".env")).ok(); dotenv::from_path(manifest_path.join(".env")).ok();
@ -89,8 +51,18 @@ pub fn demo() {
println!("------- Demo for the SSV library --------"); println!("------- Demo for the SSV library --------");
ssv_init_lib_2(); ssv_init_lib_2();
ssv_lire_carte_ps();
let code_pin = "1234";
let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0";
let lecteurvitale = "HID Global OMNIKEY 3x21 Smart Card Reader 1";
let dateconsultation ="20240813";
let carte_ps = lire_carte(code_pin, lecteur).unwrap();
println!("CartePS: {:#?}", carte_ps);
ssv_lire_config(); ssv_lire_config();
let carte_vitale = LireDroitsVitale(lecteur, lecteurvitale, code_pin, dateconsultation).unwrap();
println!("carte_vitale: {:#?}", carte_vitale);
println!("-----------------------------------------"); println!("-----------------------------------------");
} }

View File

@ -3,3 +3,4 @@ EXPORTS
SSV_InitLIB2 SSV_InitLIB2
SSV_LireCartePS SSV_LireCartePS
SSV_LireConfig SSV_LireConfig
SSV_LireDroitsVitale

View File

@ -0,0 +1,26 @@
@echo off
rem Set variables
set LIB_DIR=crates/sesam-vitale/lib
set DEF_DIR=crates/sesam-vitale/src/win/fsv
if "%1"=="/clean" (
goto clean
)
rem Set the environment for the x64 platform
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
rem Create a ssvw64.lib file from the ssvw64.def file
lib /def:ssvw64.def /out:%LIB_DIR%\ssvw64.lib /machine:x64
rem Build complete
pause
exit /b 0
:clean
del %LIB_DIR%\*.lib
del %LIB_DIR%\*.exp
rem Clean complete
pause
exit /b 0

View File

@ -1,7 +1,7 @@
@echo off @echo off
rem Set variables rem Set variables
set LIB_DIR=crates/sesam-vitale/lib set LIB_DIR=F:\PAPILLON\Krys4lideCopie\crates\sesam-vitale\lib
set DEF_DIR=crates/sesam-vitale/src/win/fsv set DEF_DIR=F:\PAPILLON\Krys4lideCopie\crates\sesam-vitale\src\win\fsv
if "%1"=="/clean" ( if "%1"=="/clean" (
goto clean goto clean

6
scripts/ssvw64.def Normal file
View File

@ -0,0 +1,6 @@
LIBRARY "ssvw64"
EXPORTS
SSV_InitLIB2
SSV_LireCartePS
SSV_LireConfig
SSV_LireDroitsVitale