8 Commits

104 changed files with 402 additions and 11389 deletions

View File

@ -1,3 +1,2 @@
SESAM_FSV_VERSION=1.40.13 SESAM_FSV_VERSION=1.40.13
SESAM_INI_PATH=/etc/opt/santesocial/fsv/${SESAM_FSV_VERSION}/conf/sesam.ini SESAM_INI_PATH=/etc/opt/santesocial/fsv/${SESAM_FSV_VERSION}/conf/sesam.ini
DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc

View File

@ -1,3 +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
DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc

View File

@ -1,28 +0,0 @@
name: Demande de fusion (Pull Request)
about: Créez une demande de fusion pour partager votre travail avec le reste de l'équipe
body:
- type: markdown
attributes:
value: |
Une demande de fusion (Pull Request) a pour objectif de
partager au reste de l'équipe un développement réalisé.
Décrire les modifications apportées, leur impact et le contexte
dans lequel elles ont été réalisées permettra aux relecteurices
de plus facilement comprendre et valider votre travail.
- type: textarea
id: details
attributes:
label: Détails
description: Décrivez le contenu de la PR, son impact concret
validations:
required: true
- type: textarea
id: why
attributes:
label: Pourquoi ?
description: Pourquoi ces modifications sont elles nécessaires ? Dans quel contexte s'inscrivent-elles ?
- type: textarea
id: documentation
attributes:
label: Documentation
description: Précisez ici des références à des ressources que vous avez utilisées pour réaliser ces modifications

View File

@ -1,33 +0,0 @@
name: Rapport de bug
about: Remplissez un rapport d'erreur
title: "[Bug]: "
blank_issues_enabled: false
labels:
- bug
- to-triage
body:
- type: textarea
id: what-happened
attributes:
label: Que se passe-t-il ?
description: Décrivez la situation que vous rencontrez
validations:
required: true
- type: textarea
id: environment-description
attributes:
label: Si le problème semble lié à votre environement, décrivez-le ici
placeholder: Windows 10, Firefox 89.0, etc.
- type: dropdown
id: module
attributes:
label: Ce problème est il relatif à un ou des modules en particulier ?
multiple: true
options:
- Interface utilisateur⋅ice (crates/app)
- Encapsulation Tauri (crates/desktop)
- Moteur SESAM-Vitale (crates/sesam-vitale)
- Librairie utilitaire (crates/utils)
- Documentation (docs)
- Scripts (scripts)
- Autre

View File

@ -1,13 +0,0 @@
name: Proposez une fonctionnalité / amélioration
about: Proposez vos idées de fonctionnalités ou d'améliorations
blank_issues_enabled: false
labels:
- feature
- to-triage
body:
- type: textarea
id: description
attributes:
label: Décrivez votre idée
validations:
required: true

View File

@ -1,20 +0,0 @@
name: Posez une question
about: Une interrogation, une difficulté ? Posez votre question
blank_issues_enabled: false
labels:
- question
- to-triage
body:
- type: textarea
id: question
attributes:
label: Que se passe-t-il ?
description: Décrivez la situation que vous rencontrez, posez votre question
validations:
required: true
- type: textarea
id: environment-description
attributes:
label: Précisez votre environnement
description: S'il vous semble pertinent de préciser votre environement, décrivez-le ici
placeholder: Windows 10, Firefox 89.0, etc.

28
.gitignore vendored
View File

@ -1,28 +1,6 @@
# ---> Rust # Ignore Rust target directory
# Generated by Cargo /target
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Ignore .env files # Ignore .env files
.env .env
.env.build
# Development Database
*.sqlite

View File

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

7619
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,12 @@
[workspace] [package]
resolver = "2" name = "utils-debug-c-lib"
members = [ version = "0.1.0"
"crates/app", edition = "2021"
"crates/sesam-vitale", build = "build.rs"
"crates/desktop",
"crates/utils",
"migration",
"entity",
".",
]
[workspace.dependencies] [dependencies]
anyhow = "1.0" dotenv = "0.15"
axum = "0.7.5" libc = "0.2"
[build-dependencies]
dotenv = "0.15" dotenv = "0.15"
sea-orm-cli = "1.0.1"
sea-orm = "1.0.1"
thiserror = "1.0"
tokio = "1.39.1"

137
README.md
View File

@ -1,123 +1,46 @@
# Krys4lide ## Requirements
Logiciel de Pharmacie libre et open-source. - Installer le [package FSV](https://industriels.sesam-vitale.fr/group/fournitures-sesam-vitale)
- Les librairies dynamiques (.lib, .dll, ...) fournies ne sont pas installés dans les emplacements standard du système, il faudra donc configurer leur chemin d'installation dans le fichier de configuration `.env.build` (voir ci-dessous)
- Le détail des chemins d'installation est donné dans la documentation du package FSV `fsv-mi-004_pack-FSV1.40.14_V2.3.pdf`
- Linux - par défaut : `/opt/santesocial/fsv/1.40.13/lib`
- Windows - par défaut : `C:\Program Files\santesocial\santesocial\fsv\1.40.14\lib` (ou dans Program Files (x86) si c'est le package 32bits qui a été installé)
## Crates - Installer la [CryptolibCPS](https://industriels.sesam-vitale.fr/group/galss-cryptolib-cps)
- Ce package fourni également l'utilitaire "CPS Gestion" pour obtenir des informations sur le lecteur de carte, etc.
- Linux : `cpgeslux`
- Windows : `...`
- `app`: Interface du logiciel, servie par un serveur web propulsé par Axum. Utilisable en mode endpoint ou encapsulé dans le client `desktop` ## Setup
- `desktop`: Client desktop propulsé par Tauri, encapsulant le serveur web `app`
- `sesam-vitale`: Bibliothèque de gestion des services SESAM-Vitale (Lecture des cartes CPS et Vitale, téléservices ...)
- `utils`: Bibliothèque de fonctions utilitaires
## Installation - Créer et éditer le fichier de configuration de build `.env.build` en s'inspirant d'un des fichiers d'exemple (`.env.build.linux.example`, `.env.build.win.example`...)
- Ce fichier est nécessaire pour le build du package Rust
### Fichiers de configuration - Créer et éditer le fichier de configuration de l'exécution `.env` en s'inspirant d'un des fichiers d'exemple (`.env.linux.example`, `.env.win.example`...)
- Ce fichier est nécessaire pour l'exécution du package Rust compilé, et doit donc être présent aux côtés de l'exécutable généré, le cas échéant
Certaines librairies nécessitent de définir certaines paramètres de configuration pour fonctionner correctement, en particulier le moteur SESAM-Vitale.
Ces paramètres sont définis dans un fichier de configuration `.env` situé dans un des dossiers suivant (par ordre de priorité) :
- dans le dossier courant (`./.env`)
- dans le dossier du manifeste (par exemple `crates/sesam-vitale/.env`)
- dans le dossier de configuration standard de l'OS (par exemple, sur linux, `~/.config/krys4lide/.env` - [plus d'info](https://github.com/dirs-dev/directories-rs?tab=readme-ov-file#projectdirs))
Des exemples de fichiers de configuration sont disponibles à la racine du projet : `.env.linux.example` et `.env.win.example`.
## Development
### Pré-requis
#### Tauri CLI
La CLI Tauri est nécessaire au lancement du client `desktop`. Elle peut être installée via Cargo :
```bash
cargo install tauri-cli --version "^2.0.0-beta"
```
#### 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)
#### SeaORM CLI
SeaORM est notre ORM. Le CLI SeaORM est nécessaire pour la génération des modèles de la base de données et des migrations associées. Elle peut être installée via Cargo :
```bash
cargo install sea-orm-cli
```
L'applicatif va chercher les informations de connexion à la base de données dans la variable `DATABASE_URL` importée depuis les [fichiers de configuration](#fichiers-de-configuration).
```.env
DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc
```
Toutefois, l'usage de la CLI de SeaORM nécessite de renseigner les informations de connexion à la base de données dans un fichier `.env` situé à la racine du projet.
> Astuce : utilisé un lien symbolique pour éviter de dupliquer le fichier `.env`.
#### 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
cargo tauri dev
```
/!\ 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
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 ### Compilation C
```bash Ce package s'appuie sur deux librairies :
cargo tauri build - Une librairie statique, compilée à partir des sources (`*.c`, `*.h`) fournies dans le dossier `./src`
``` - Une librairie dynamique, fournie par le package FSV
- Windows : on fournit les headers, non présents dans la `.dll` en compilant les fichiers `src/*.def` en leur version binaire `lib/*.lib`
## Gestion de la base de données Pour compiler les fichiers de librairie :
### Création d'une migration - Windows : `.\make.bat`
- Linux : `make`
```bash Pour nettoyer le dossier `./lib` :
sea-orm-cli migrate generate <nom_de_la_migration>
```
Cette commande génère un fichier de migration à adapter dans le dossier `migration/src`. - Windows : `.\make.bat /clean`
- Linux : `make clean`
### Appliquer les migrations ### Compilation Rust
```bash `cargo build`
sea-orm-cli migrate up
```
### Génération des entitées ## Run
```bash `cargo run`
sea-orm-cli generate entity -o entity/src/entities
```

26
build.rs Normal file
View File

@ -0,0 +1,26 @@
extern crate dotenv;
use std::env;
use std::path::PathBuf;
fn main() {
dotenv::from_filename(".env.build").ok();
println!("cargo::rerun-if-changed=.env.build");
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let static_lib_path = PathBuf::from(manifest_dir).join("lib");
println!("cargo::rustc-link-search=native={}", static_lib_path.display());
println!("cargo::rustc-link-lib=static=p4pillondebuglib");
let fsv_lib_path = PathBuf::from(env::var("SESAM_FSV_LIB_PATH").unwrap());
println!("cargo::rustc-link-search=native={}", fsv_lib_path.display());
println!("cargo::rustc-link-lib=dylib={}", env::var("SESAM_FSV_SSVLIB").unwrap());
if cfg!(target_os = "windows") {
let path = env::var("PATH").unwrap_or(String::new());
println!("cargo:rustc-env=PATH={};{}", fsv_lib_path.display(), path);
} else if cfg!(target_os = "linux") {
println!("cargo:rustc-env=LD_LIBRARY_PATH={}", fsv_lib_path.display());
}
}

View File

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

View File

@ -1,33 +0,0 @@
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
askama = "0.12.1"
askama_axum = "0.4.0"
axum.workspace = true
axum-htmx = { version = "0.6", features = ["auto-vary"] }
futures = "0.3.30"
listenfd = "1.0.1"
notify = "6.1.1"
sea-orm = { workspace = true, features = [
# Same `ASYNC_RUNTIME` and `DATABASE_DRIVER` as in the migration crate
"sqlx-sqlite",
"runtime-tokio-rustls",
"macros",
] }
serde = { version = "1.0.204", features = ["derive"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5.2", features = ["fs"] }
tower-livereload = "0.9.3"
entity = { path = "../../entity" }
migration = { path = "../../migration" }
utils = { path = "../utils" }
[dev-dependencies]
cargo-watch = "8.5.1"
systemfd = "0.4.0"
sea-orm-cli.workspace = true

View File

@ -1,44 +0,0 @@
## Pré-requis
- Récupérer le binaire TailwindCSS : https://tailwindcss.com/blog/standalone-cli
## Configuration
> Astuce : lorsqu'on exécute directement la crate `App` à des fins de développement, le système de configuration n'utilisera pas l'éventuel fichier `.env` situé à la racine du workspace Rust. Pour éviter de dupliquer le fichier `.env`, il est possible de créer un lien symbolique vers le fichier `.env` de la crate `App` :
```bash
cd crates/app
ln -s ../../.env .env
```
## 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_.

View File

@ -1,6 +0,0 @@
[general]
# Directories to search for templates, relative to the crate root.
dirs = [
"src/pages",
"src/components",
]

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -1,18 +0,0 @@
{% set selected = item.id == current %}
<li>
<a
href="{{ item.href }}"
{% if selected -%}
class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
aria-current="page"
{% else -%}
class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
{% endif -%}
hx-get="{{ item.href }}"
hx-push-url="true"
hx-swap="outerHTML"
hx-select-oob="#menu-items,#page-header,#page-main"
>
{{ item.label }}
</a>
</li>

View File

@ -1,50 +0,0 @@
{% macro navbar(current) %}
{% let items=crate::menu::get_menu_items() %}
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="/" class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Krys4lide</span>
</a>
<div class="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
<button type="button" class="flex text-sm bg-gray-800 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" id="user-menu-button" aria-expanded="false" data-dropdown-toggle="user-dropdown" data-dropdown-placement="bottom">
<span class="sr-only">Ouvrir le menu de profil</span>
<img class="w-8 h-8 rounded-full" src="https://flowbite.com/docs/images/people/profile-picture-3.jpg" alt="user photo">
</button>
<!-- Dropdown menu -->
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-dropdown">
<div class="px-4 py-3">
<span class="block text-sm text-gray-900 dark:text-white">Bonnie Green</span>
<span class="block text-sm text-gray-500 truncate dark:text-gray-400">name@flowbite.com</span>
</div>
<ul class="py-2" aria-labelledby="user-menu-button">
<li>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Profile</a>
</li>
<li>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Settings</a>
</li>
<li>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Sign out</a>
</li>
</ul>
</div>
<button data-collapse-toggle="navbar-user" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-user" aria-expanded="false">
<span class="sr-only">Ouvrir le menu de navigation</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
</svg>
</button>
</div>
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
<ul id="menu-items" class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
{% for item in items %}
{% include "navbar/menu-item.html" %}
{% endfor %}
</ul>
</div>
</div>
</nav>
{% endmacro %}

View File

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

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

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

View File

@ -1,11 +0,0 @@
use migration::{Migrator, MigratorTrait};
use sea_orm::{Database, DatabaseConnection, DbErr};
use std::env;
pub async fn get_connection() -> Result<DatabaseConnection, DbErr> {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let db_connection = Database::connect(database_url).await?;
Migrator::up(&db_connection, None).await?;
Ok(db_connection)
}

View File

@ -1,47 +0,0 @@
use std::path::PathBuf;
use axum::http::{StatusCode, Uri};
use axum_htmx::AutoVaryLayer;
use sea_orm::DatabaseConnection;
use thiserror::Error;
use tower_http::services::ServeDir;
use ::utils::config::{load_config, ConfigError};
pub mod db;
mod menu;
mod pages;
async fn fallback(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
}
#[derive(Error, Debug)]
pub enum InitError {
#[error(transparent)]
ConfigError(#[from] ConfigError),
}
pub fn init() -> Result<(), InitError> {
load_config(None)?;
Ok(())
}
#[derive(Clone)]
pub struct AppState {
db_connection: DatabaseConnection,
}
pub async fn get_router(assets_path: PathBuf) -> axum::Router<()> {
let db_connection = db::get_connection().await.unwrap();
let state: AppState = AppState { db_connection };
axum::Router::new()
.nest_service("/assets", ServeDir::new(assets_path))
.merge(pages::get_routes())
.fallback(fallback)
.with_state(state)
// The AutoVaryLayer is used to avoid cache issues with htmx (cf: https://github.com/robertwayne/axum-htmx?tab=readme-ov-file#auto-caching-management)
.layer(AutoVaryLayer)
}

View File

@ -1,90 +0,0 @@
use std::path::{Path, PathBuf};
use std::{env, io};
use axum::body::Body;
use axum::http::Request;
use listenfd::ListenFd;
use notify::Watcher;
use thiserror::Error;
use tokio::net::TcpListener;
use tower_livereload::predicate::Predicate;
use tower_livereload::LiveReloadLayer;
use ::app::{get_router, init, InitError};
#[derive(Error, Debug)]
pub enum AppError {
#[error("Unable to bind to TCP listener")]
TCPListener(#[from] std::io::Error),
#[error("Error with the notify watcher")]
NotifyWatcher(#[from] notify::Error),
#[error("Missing environment variable {var}")]
MissingEnvVar { var: &'static str },
#[error("Error with the database connection")]
DatabaseConnection(#[from] sea_orm::DbErr),
#[error("Error while initialising the app")]
Initialisation(#[from] InitError),
}
/// 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() -> Result<TcpListener, io::Error> {
let mut listenfd = ListenFd::from_env();
match listenfd.take_tcp_listener(0)? {
// if we are given a tcp listener on listen fd 0, we use that one
Some(listener) => {
listener.set_nonblocking(true)?;
Ok(TcpListener::from_std(listener)?)
}
// otherwise fall back to local listening
None => Ok(TcpListener::bind(DEFAULT_LISTENER).await?),
}
}
fn get_livereload_layer(
templates_paths: Vec<PathBuf>,
) -> Result<LiveReloadLayer<NotHtmxPredicate>, notify::Error> {
let livereload = LiveReloadLayer::new();
let reloader = livereload.reloader();
let mut watcher = notify::recommended_watcher(move |_| reloader.reload())?;
for templates_path in templates_paths {
watcher.watch(templates_path.as_path(), notify::RecursiveMode::Recursive)?;
}
Ok(livereload.request_predicate::<Body, NotHtmxPredicate>(NotHtmxPredicate))
}
#[tokio::main]
async fn main() -> Result<(), AppError> {
init()?;
let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| AppError::MissingEnvVar {
var: "CARGO_MANIFEST_DIR",
})?;
let assets_path = Path::new(&manifest_dir).join("assets");
let templates_paths = vec![
Path::new(&manifest_dir).join("src/pages"),
Path::new(&manifest_dir).join("src/components"),
];
let livereload_layer =
get_livereload_layer(templates_paths).map_err(AppError::NotifyWatcher)?;
let router = get_router(assets_path).await.layer(livereload_layer);
let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?;
let local_addr = listener.local_addr().map_err(AppError::TCPListener)?;
println!("Listening on: http://{}", local_addr);
// Run the server with the router
axum::serve(listener, router.into_make_service()).await?;
Ok(())
}

View File

@ -1,28 +0,0 @@
pub struct MenuItem {
pub id: String,
pub label: String,
pub href: String,
}
/// Get the menu items
/// This function is the central place to define the menu items
/// It can be used directly in templates, for example in the `navbar` component to render the menu
pub fn get_menu_items() -> Vec<MenuItem> {
vec![
MenuItem {
id: "home".to_string(),
label: "Accueil".to_string(),
href: "/".to_string(),
},
MenuItem {
id: "cps".to_string(),
label: "CPS".to_string(),
href: "/cps".to_string(),
},
MenuItem {
id: "debug".to_string(),
label: "DEBUG".to_string(),
href: "/debug".to_string(),
},
]
}

View File

@ -1,43 +0,0 @@
{% extends "base.html" %}
{% import "navbar/navbar.html" as navbar -%}
{% block title %}Pharma Libre - CPS{% endblock %}
{% block body %}
{% call navbar::navbar(current="cps") %}
<div class="py-10">
<header id="page-header">
<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"
>
CPS
</h1>
</div>
</header>
<main id="page-main">
<div
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
>A</div>
<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"
>B</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>C</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>D</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>E</div>
</div>
</div>
</main>
</div>
{% endblock %}

View File

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

View File

@ -1,72 +0,0 @@
{% extends "base.html" %}
{% import "navbar/navbar.html" as navbar -%}
{% block title %}Pharma Libre - Debug{% endblock %}
{% block body %}
{% call navbar::navbar(current="debug") %}
<div class="py-10">
<header id="page-header">
<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">
DEBUG
</h1>
</div>
</header>
<main id="page-main">
<div class="mx-auto max-w-7xl py-12 sm:px-6 lg:px-8">
<div class="mx-auto max-w-3xl">
<div
class="w-full p-6 bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md dark:bg-gray-800 dark:border-gray-700 sm:p-8">
<h1
class="mb-1 text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Base de données
</h1>
<p class="font-light text-gray-500 dark:text-gray-400 mb-5">
Données extraites de la base de donnée à des fins de debug
</p>
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
ID
</th>
<th scope="col" class="px-6 py-3">
Value
</th>
</tr>
</thead>
<tbody>
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
db_ping_status
</th>
<td class="px-6 py-4">
{{ db_ping_status }}
</td>
</tr>
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
debug_entries_count
</th>
<td class="px-6 py-4">
{{ debug_entries_count }}
</td>
</tr>
</tbody>
</table>
<div class="mt-4 space-y-4 lg:mt-5 md:space-y-5">
<button type="button"
class="w-full text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
hx-trigger="click" hx-post="/debug/add_random" hx-swap="none">
Add random debug entry
</button>
</div>
</div>
</div>
</div>
</main>
</div>
{% endblock %}

View File

@ -1,48 +0,0 @@
use askama_axum::Template;
use axum::{extract::State, routing};
use ::entity::{debug, debug::Entity as DebugEntity};
use axum_htmx::HxRequest;
use sea_orm::*;
use crate::AppState;
async fn get_debug_entries(db: &DatabaseConnection) -> Result<Vec<debug::Model>, DbErr> {
DebugEntity::find().all(db).await
}
async fn add_random_debug_entry(State(AppState { db_connection }): State<AppState>) {
let random_entry = debug::ActiveModel {
title: Set("Random title".to_string()),
text: Set("Random text".to_string()),
..Default::default()
};
random_entry.insert(&db_connection).await.unwrap();
}
#[derive(Template)]
#[template(path = "debug.html")]
struct GetDebugTemplate {
hx_request: bool,
db_ping_status: bool,
debug_entries_count: usize,
}
async fn debug(
HxRequest(hx_request): HxRequest,
State(AppState { db_connection }): State<AppState>,
) -> GetDebugTemplate {
let db_ping_status = db_connection.ping().await.is_ok();
let debug_entries = get_debug_entries(&db_connection).await.unwrap();
GetDebugTemplate {
hx_request,
db_ping_status,
debug_entries_count: debug_entries.len(),
}
}
pub fn get_routes() -> axum::Router<crate::AppState> {
axum::Router::new()
.route("/", routing::get(debug))
.route("/add_random", routing::post(add_random_debug_entry))
}

View File

@ -1,43 +0,0 @@
{% extends "base.html" %}
{% import "navbar/navbar.html" as navbar -%}
{% block title %}Pharma Libre - Accueil{% endblock %}
{% block body %}
{% call navbar::navbar(current="home") %}
<div class="py-10">
<header id="page-header">
<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"
>
Accueil
</h1>
</div>
</header>
<main id="page-main">
<div
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
>
<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"
>A</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>B</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>C</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>D</div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
>E</div>
</div>
</main>
</div>
{% endblock %}

View File

@ -1,12 +0,0 @@
use askama_axum::Template;
use axum_htmx::HxRequest;
#[derive(Template)]
#[template(path = "home.html")]
pub struct GetHomeTemplate {
hx_request: bool,
}
pub async fn home(HxRequest(hx_request): HxRequest) -> GetHomeTemplate {
GetHomeTemplate { hx_request }
}

View File

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

View File

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

View File

@ -1,7 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

View File

@ -1,25 +0,0 @@
[package]
name = "desktop"
version = "0.1.0"
description = "Un logiciel de pharmacie libre et open-source."
authors = ["p4pillon"]
edition = "2021"
[lib]
name = "desktop_lib"
crate-type = ["lib", "cdylib", "staticlib"]
[build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
axum.workspace = true
bytes = "1.6.1"
http = "1.1.0"
tauri = { version = "2.0.0-beta", features = [] }
thiserror.workspace = true
tower = "0.4.13"
tokio.workspace = true
app = { path = "../app" }

View File

@ -1,3 +0,0 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,92 +0,0 @@
use axum::body::{to_bytes, Body};
use axum::Router;
use bytes::Bytes;
use http::{request, response, Request, Response};
use std::path::PathBuf;
use std::sync::Arc;
use tauri::path::BaseDirectory;
use tauri::Manager;
use thiserror::Error;
use tokio::sync::{Mutex, MutexGuard};
use tower::{Service, ServiceExt};
use ::app::init;
#[derive(Error, Debug)]
pub enum DesktopError {
#[error("Axum error:\n{0}")]
Axum(#[from] axum::Error),
#[error("Infallible error")]
Infallible(#[from] std::convert::Infallible),
}
/// Process requests sent to Tauri (with the `axum://` protocol) and handle them with Axum
/// When an error occurs, this function is expected to panic, which should result in a 500 error
/// being sent to the client, so we let the client handle the error recovering
async fn process_tauri_request(
tauri_request: Request<Vec<u8>>,
mut router: MutexGuard<'_, Router>,
) -> Result<Response<Vec<u8>>, DesktopError> {
let (parts, body): (request::Parts, Vec<u8>) = tauri_request.into_parts();
let axum_request: Request<Body> = Request::from_parts(parts, body.into());
let axum_response: Response<Body> = router
.as_service()
.ready()
.await
.map_err(DesktopError::Infallible)?
.call(axum_request)
.await
.map_err(DesktopError::Infallible)?;
let (parts, body): (response::Parts, Body) = axum_response.into_parts();
let body: Bytes = to_bytes(body, usize::MAX).await?;
let tauri_response: Response<Vec<u8>> = Response::from_parts(parts, body.into());
Ok(tauri_response)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
init().expect("Failed to initialize the application");
tauri::Builder::default()
.setup(|app| {
let assets_path: PathBuf = app
.path()
.resolve("assets", BaseDirectory::Resource)
.expect("Assets path should be resolvable");
// Adds Axum router to application state
// This makes it so we can retrieve it from any app instance (see bellow)
let router = Arc::new(Mutex::new(app::get_router(assets_path)));
app.manage(router);
Ok(())
})
.register_asynchronous_uri_scheme_protocol("axum", move |app, request, responder| {
// Retrieve the router from the application state and clone it for the async block
let router = Arc::clone(&app.state::<Arc<Mutex<axum::Router>>>());
// Spawn a new async task to process the request
tauri::async_runtime::spawn(async move {
let router = router.lock().await;
match process_tauri_request(request, router).await {
Ok(response) => responder.respond(response),
Err(err) => {
let body = format!("Failed to process an axum:// request:\n{}", err);
responder.respond(
http::Response::builder()
.status(http::StatusCode::BAD_REQUEST)
.header(http::header::CONTENT_TYPE, "text/plain")
.body::<Vec<u8>>(body.into())
.expect("BAD_REQUEST response should be valid"),
)
}
}
});
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -1,6 +0,0 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
desktop_lib::run()
}

View File

@ -1,41 +0,0 @@
{
"productName": "Logiciel Pharma",
"version": "0.0.1",
"identifier": "org.p4pillon.pharma.desktop",
"build": {
"beforeDevCommand": {
"cwd": "../app",
"script": "cargo run"
},
"devUrl": "http://localhost:3000",
"frontendDist": "axum://place.holder/"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "Logiciel Pharma",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"resources": {
"../app/assets/": "./assets/"
},
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@ -1,9 +0,0 @@
# Ignore Rust target directory
/target
# Ignore .env files
.env
.env.build
# Ignore exploitation files - only usefull for local debugging on windows
lib/*.exp

View File

@ -1,14 +0,0 @@
[package]
name = "sesam-vitale"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
libc = "0.2"
thiserror.workspace = true
utils = { path = "../utils" }
[build-dependencies]
dotenv.workspace = true

View File

@ -1,34 +0,0 @@
## Requirements
- Installer le [package FSV](https://industriels.sesam-vitale.fr/group/fournitures-sesam-vitale)
- Les librairies dynamiques (.lib, .dll, ...) fournies ne sont pas installés dans les emplacements standard du système, il faudra donc configurer leur chemin d'installation dans le fichier de configuration `.env.build` (voir ci-dessous)
- Le détail des chemins d'installation est donné dans la documentation du package FSV `fsv-mi-004_pack-FSV1.40.14_V2.3.pdf`
- Linux - par défaut : `/opt/santesocial/fsv/1.40.13/lib`
- Windows - par défaut : `C:\Program Files\santesocial\santesocial\fsv\1.40.14\lib` (ou dans Program Files (x86) si c'est le package 32bits qui a été installé)
- Installer la [CryptolibCPS](https://industriels.sesam-vitale.fr/group/galss-cryptolib-cps)
- Ce package fourni également l'utilitaire "CPS Gestion" pour obtenir des informations sur le lecteur de carte, etc.
- Linux : `cpgeslux`
- Windows : `...`
## Setup
- Créer et éditer le fichier de configuration de build `.env.build` en s'inspirant d'un des fichiers d'exemple (`.env.build.linux.example`, `.env.build.win.example`...)
- Ce fichier est nécessaire pour le build du package Rust
- Créer et éditer le fichier de configuration de l'exécution `.env` en s'inspirant d'un des fichiers d'exemple (`.env.linux.example`, `.env.win.example`...)
- Ce fichier est nécessaire pour l'exécution du package Rust compilé, et doit donc être présent aux côtés de l'exécutable généré, le cas échéant
## Build
### Windows - Compilation des headers FSV
Sous windows, la librairie dynamique fournie par le package FSV nécessite des headers qui ne sont pas présents dans la `.dll`. Il est donc nécessaire de fournir ces headers, en les renseignant dans des fichiers `crates/sesam-vitale/src/win/fsv/*.def` qui seront compilés en leur version binaire `crates/sesam-vitale/lib/*.lib`.
En cas de modification des fichiers `.def`, pour re-compiler ces headers, faire appel au script `scripts/compile_win_headers.bat`.
| /!\ Attention, le script `compile_win_headers.bat` exécute, en interne, l'utilitaire `vcvarsall.bat` et le linker `lib.exe` de Visual Studio. Visual Studio doit donc être installé et le chemin vers l'intallation le script `vcvarsall.bat`, écrit en dur dans le script `compile_win_headers.bat` doit être adapté à votre installation.
## À creuser
- Compilation cross platform facilitée par du Docker : https://github.com/cross-rs/cross
- Pour éviter l'usage de dotenv pour la configuration, on peut utiliser https://direnv.net/

View File

@ -1,43 +0,0 @@
use std::env;
use std::path::PathBuf;
use dotenv::from_path;
fn main() {
// Load the .env.build file for build-time environment variables
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set");
let manifest_path = PathBuf::from(manifest_dir);
from_path(manifest_path.join(".env.build")).ok();
println!("cargo::rerun-if-env-changed=SESAM_FSV_LIB_PATH");
println!("cargo::rerun-if-env-changed=SESAM_FSV_SSVLIB");
println!("cargo::rerun-if-changed=.env.build");
println!("cargo::rerun-if-changed=build.rs");
// Add local lib directory to the linker search path (for def files and static libs)
let static_lib_path = manifest_path.join("lib");
println!(
"cargo::rustc-link-search=native={}",
static_lib_path.display()
);
// Add the SESAM_FSV_LIB_PATH to the linker search path
let fsv_lib_path =
PathBuf::from(env::var("SESAM_FSV_LIB_PATH").expect("SESAM_FSV_LIB_PATH must be set"));
println!("cargo::rustc-link-search=native={}", fsv_lib_path.display());
// Add the SESAM_FSV_LIB_PATH to the PATH environment variable
if cfg!(target_os = "windows") {
let path = env::var("PATH").unwrap_or_default();
println!("cargo:rustc-env=PATH={};{}", fsv_lib_path.display(), path);
} else if cfg!(target_os = "linux") {
println!("cargo:rustc-env=LD_LIBRARY_PATH={}", fsv_lib_path.display());
}
// Link the SESAM_FSV_SSVLIB dynamic library
println!(
"cargo::rustc-link-lib=dylib={}",
env::var("SESAM_FSV_SSVLIB").expect("SESAM_FSV_SSVLIB must be set")
);
// TODO : try `raw-dylib` instead of `dylib` on Windows to avoid the need of the `lib` headers compiled from the `def`
}

View File

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

View File

@ -1,19 +0,0 @@
pub mod cps;
pub mod libssv;
pub mod ssv_memory;
pub mod ssvlib_demo;
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@ -1,30 +0,0 @@
/// 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};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum LibSSVError {
#[error("SSV library error in {function}: {code}")]
StandardErrorCode { code: u16, function: &'static str },
}
#[cfg_attr(target_os = "linux", link(name = "ssvlux64"))]
#[cfg_attr(target_os = "windows", link(name = "ssvw64"))]
extern "C" {
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_LireConfig(
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

View File

@ -1,10 +0,0 @@
mod cps;
mod libssv;
mod ssv_memory;
mod ssvlib_demo;
use anyhow::{Context, Result};
fn main() -> Result<()> {
ssvlib_demo::demo().context("Error while running the SSV library demo")
}

View File

@ -1,358 +0,0 @@
/// # SSV Memory
/// Provide functions to manipulate raw memory from SSV library.
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BytesReadingError {
#[error("Empty bytes input")]
EmptyBytes,
#[error("Invalid memory: not enough bytes ({actual}) to read the expected size ({expected})")]
InvalidSize { expected: usize, actual: usize },
#[error("Invalid memory: size ({actual}) is expected to be less than {expected} bytes")]
SizeTooBig { expected: usize, actual: usize },
#[error("Invalid memory: not enough bytes to read the block id")]
InvalidBlockId(#[from] std::array::TryFromSliceError),
#[error("Error while reading field at offset {offset}")]
InvalidField {
source: Box<BytesReadingError>,
offset: usize,
},
}
#[derive(Debug, Error)]
pub enum SSVMemoryError {
#[error("Error while parsing block at offset {offset}")]
BlockParsing {
source: BytesReadingError,
offset: usize,
},
}
#[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 = BytesReadingError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.is_empty() {
return Err(BytesReadingError::EmptyBytes);
}
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(BytesReadingError::InvalidSize {
expected: size_bytes_len,
actual: bytes.len() - 1,
});
} else if size_bytes_len > 4 {
return Err(BytesReadingError::SizeTooBig {
expected: 4,
actual: size_bytes_len,
});
}
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> TryFrom<&'a [u8]> for Block<'a> {
type Error = BytesReadingError;
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
let mut offset = 0;
let id = u16::from_be_bytes(
bytes[..2]
.try_into()
.map_err(BytesReadingError::InvalidBlockId)?,
);
offset += 2;
let ElementSize {
size: block_size,
pad,
} = bytes[2..].try_into()?;
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..].try_into().map_err(|err| {
BytesReadingError::InvalidField {
source: Box::new(err),
offset: field_offset,
}
})?;
field.id = field_id;
field_offset += field.size;
field_id += 1;
content.push(field);
}
Ok(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> TryFrom<&'a [u8]> for Field<'a> {
type Error = BytesReadingError;
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
let ElementSize { size, pad } = bytes.try_into()?;
let contenu = &bytes[pad..pad + size];
Ok(Field {
id: 0,
size: pad + size,
content: contenu,
})
}
}
pub fn decode_ssv_memory(bytes: &[u8], size: usize) -> Result<Vec<Block>, SSVMemoryError> {
let mut blocks: Vec<Block> = Vec::new();
let mut offset = 0;
while offset < size {
let block: Block =
bytes[offset..]
.try_into()
.map_err(|err| SSVMemoryError::BlockParsing {
source: err,
offset,
})?;
offset += block.size;
blocks.push(block);
}
Ok(blocks)
}
#[cfg(test)]
mod test_element_size {
use std::any::Any;
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, BytesReadingError> = bytes.try_into();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().type_id(),
BytesReadingError::EmptyBytes.type_id()
);
}
#[test]
fn invalid_memory() {
let bytes: &[u8] = &[0b_1000_0001_u8];
let result: Result<ElementSize, BytesReadingError> = bytes.try_into();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
BytesReadingError::InvalidSize {
expected: 1,
actual: 0
}
.to_string()
);
let bytes: &[u8] = &[0b_1000_0010_u8, 1];
let result: Result<ElementSize, BytesReadingError> = bytes.try_into();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
BytesReadingError::InvalidSize {
expected: 2,
actual: 1
}
.to_string()
);
let bytes: &[u8] = &[0b_1000_0101_u8, 1, 1, 1, 1, 1];
let result: Result<ElementSize, BytesReadingError> = bytes.try_into();
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
BytesReadingError::SizeTooBig {
expected: 4,
actual: 5
}
.to_string()
);
}
}
#[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.try_into().unwrap();
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.try_into().unwrap();
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.try_into().unwrap();
assert_eq!(field1.size, 2);
assert_eq!(field1.content, &[48]);
let field2: Field = bytes[field1.size..].try_into().unwrap();
assert_eq!(field2.size, 2);
assert_eq!(field2.content, &[56]);
let field3: Field = bytes[field1.size + field2.size..].try_into().unwrap();
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.try_into().unwrap();
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..].try_into().unwrap();
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()).unwrap();
assert_eq!(blocks.len(), 2);
}
}

View File

@ -1,87 +0,0 @@
/// High level API for the SSV library,
/// based on the low level bindings in libssv.rs.
use libc::{c_void, size_t};
use std::env;
use std::ffi::CString;
use std::ptr;
use thiserror::Error;
use crate::cps::lire_carte;
use crate::libssv::{SSV_InitLIB2, SSV_LireConfig};
use ::utils::config::{load_config, ConfigError};
#[derive(Error, Debug)]
pub enum SSVDemoError {
#[error(transparent)]
CartePSReading(#[from] crate::cps::CartePSError),
#[error(transparent)]
SSVLibErrorCode(#[from] crate::libssv::LibSSVError),
#[error(transparent)]
Configuration(#[from] ConfigError),
}
fn ssv_init_lib_2() -> Result<(), SSVDemoError> {
let ini_str = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set");
let ini = CString::new(ini_str).expect("CString::new failed");
unsafe {
let result = SSV_InitLIB2(ini.as_ptr());
println!("SSV_InitLIB2 result: {}", result);
if result != 0 {
return Err(crate::libssv::LibSSVError::StandardErrorCode {
code: result,
function: "SSV_InitLIB2",
}
.into());
}
}
Ok(())
}
fn ssv_lire_config() -> Result<(), SSVDemoError> {
let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0;
unsafe {
let result = SSV_LireConfig(&mut buffer, &mut size);
println!("SSV_LireConfig result: {}", result);
if result != 0 {
return Err(crate::libssv::LibSSVError::StandardErrorCode {
code: result,
function: "SSV_LireConfig",
}
.into());
}
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);
}
}
Ok(())
}
pub fn demo() -> Result<(), SSVDemoError> {
// 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
println!("------- Demo for the SSV library --------");
load_config(None)?;
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)?;
println!("CartePS: {:#?}", carte_ps);
ssv_lire_config()?;
println!("-----------------------------------------");
Ok(())
}

View File

@ -1,10 +0,0 @@
[package]
name = "utils"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
directories = "5.0"
dotenv.workspace = true
thiserror.workspace = true

View File

@ -1,68 +0,0 @@
use std::{env, path::PathBuf, sync::atomic::AtomicBool};
use directories::ProjectDirs;
use dotenv::from_path;
use thiserror::Error;
const CONFIG_FILE_NAME: &str = ".env";
static CONFIG_INITIALIZED: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("No config file {0} found in the following directories: {1:#?}")]
ConfigFileNotFound(String, Vec<PathBuf>),
#[error("Failed to load config file: {0}")]
LoadConfigError(#[from] dotenv::Error),
#[error("Environment variable error: {0}")]
EnvVarError(#[from] std::env::VarError),
}
pub fn get_config_dirs() -> Vec<PathBuf> {
let mut config_dirs = vec![
PathBuf::from(""), // Current directory
];
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
config_dirs.push(PathBuf::from(manifest_dir));
}
if let Some(proj_dirs) = ProjectDirs::from("org", "P4pillon", "Krys4lide") {
config_dirs.push(proj_dirs.config_dir().to_path_buf());
}
config_dirs
}
pub fn get_config_files() -> Result<Vec<PathBuf>, ConfigError> {
let config_dirs = get_config_dirs();
let mut config_files = Vec::new();
for config_dir in config_dirs.iter() {
let config_file = config_dir.join(CONFIG_FILE_NAME);
if config_file.exists() {
config_files.push(config_file);
}
}
if config_files.is_empty() {
return Err(ConfigError::ConfigFileNotFound(
CONFIG_FILE_NAME.to_string(),
config_dirs,
));
}
Ok(config_files)
}
pub fn load_config(force: Option<bool>) -> Result<(), ConfigError> {
let force = force.unwrap_or(false);
if CONFIG_INITIALIZED.load(std::sync::atomic::Ordering::Relaxed) && force {
println!("DEBUG: Config already initialized, skipping");
return Ok(());
}
let config_files = get_config_files()?;
// Load the first config file found
// TODO: add a verbose log to list all config files found
println!(
"DEBUG: Config files found (1st loaded): {:#?}",
config_files
);
from_path(config_files[0].as_path()).map_err(ConfigError::LoadConfigError)?;
CONFIG_INITIALIZED.store(true, std::sync::atomic::Ordering::Relaxed);
Ok(())
}

View File

@ -1 +0,0 @@
pub mod config;

View File

@ -1,85 +0,0 @@
# Gestion des erreurs
Ce document décrit comment les erreurs sont gérées dans le projet.
## Gestion native
Par principe, en Rust, on évite au maximum la gestion par exception, ne la réservant qu'aux situations où un crash du programme est la meilleure solution.
En temps normal, on renvoie des `Result<Valeur, Erreur>` (pour les situations réussite/erreur) ou des `Option<Valeur>` (pour les situations valeur non-nulle/nulle).
Quand on fait face à une situation d'erreur, on cherchera à la gérer de manière explicite (voir [Récupération des erreurs](#récupération-des-erreurs)) ou à la remonter à un niveau supérieur, généralement à l'aide de l'opérateur `?`.
On évitera, par contre, au maximum de générer des exceptions (appelées "panics" en Rust), que ce soit par l'usage de `panic!` ou par des appels à des fonctions qui paniquent en cas d'erreur (comme `unwrap` ou `expect`).
De nombreux exemples des idiomes natifs de gestion des erreurs en Rust sont disponibles dans la documentation [Rust by example](https://doc.rust-lang.org/rust-by-example/error.html).
## Librairies de gestion des erreurs
Deux librairies sont utilisées pour gérer les erreurs dans le projet :
- [`anyhow`](https://docs.rs/anyhow/latest/anyhow/) : qui permet de renvoyer des erreurs faiblement typées, mais très facile à enrichir avec des messages d'explication. On l'utilise pour communiquer facilement des erreurs de haut niveau avec un utilisateur final.
```rust
use anyhow::{anyhow, Result};
fn get_cluster_info() -> Result<ClusterInfo> {
let data = fs::read_to_string("cluster.json")
.with_context(|| "failed to read cluster config")?;
let info: ClusterInfo = serde_json::from_str(&data)
.with_context(|| "failed to parse cluster config")?;
Ok(info)
}
```
- [`thiserror`](https://docs.rs/thiserror/latest/thiserror/) : qui fournit des macros pour définir des erreurs fortement typées. On l'utilise pour définir des erreurs spécifiques à une partie du code, contextualisées avec des données structurées plutôt que de simples messages d'erreurs, afin de favoriser la "récupération" face aux erreurs.
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
```
## Récupération des erreurs
Dans la mesure du possible, on essaie de privilégier la "récupération" face à une erreur plutôt que le "crash". Les stratégies de récupération sont :
- Réessayer l'opération, tel quel ou avec des paramètres différents
- Contourner l'opération, en la remplaçant par une autre
- À défaut, informer l'utilisateur de l'erreur et :
- arrêter / annuler l'opération en cours
- ignorer l'erreur et continuer l'exécution
Quand on ne peut pas récupérer une erreur, on la remonte à un niveau supérieur, si besoin en la convertissant dans un type d'erreur plus générique et approprié au niveau considéré.
## Conversion des erreurs
Quand on remonte une erreur à un niveau supérieur, on peut être amené à la convertir dans un type d'erreur plus générique et approprié au niveau considéré. Pour faciliter cette conversion, on implémente le trait `From`. Avec `thiserror`, on peut utiliser l'attribut `#[from]` ou le paramètre `source` pour automatiser l'implémentation de telles conversions.
On peut ensuite, lors de la gestion d'une erreur, on pourra :
- soit directement renvoyer l'erreur à l'aide de l'opérateur `?`, qui se chargera de la conversion ;
- soit convertir l'erreur explicitement, par exemple en utilisant la méthode `map_err` sur un `Result`, en particulier quand on veut enrichir l'erreur avec des informations supplémentaires.
## Usages exceptionnels de `unwrap` et `expect`
Provoquant des "panics" en cas d'erreur, les fonctions `unwrap` et `expect` ne doivent être utilisées que dans des cas exceptionnels :
- Dans les tests, pour signaler une erreur de test
- Au plus haut niveau de l'application, pour signaler une erreur fatale qui ne peut pas être récupérée
- Dans des situations où l'erreur ne peut pas se produire, par exemple après une vérification de préconditions
Dans l'idéal, on préférera l'usage de `expect` à `unwrap`, car il permet de donner un message d'erreur explicite.
## Ressources
- [The Rust Programming Language - Ch. 9: Error Handling](https://doc.rust-lang.org/book/ch09-00-error-handling.html)
- [The NRC Book - Error Handling in Rust](https://nrc.github.io/error-docs/intro.html)
- [The Error Design Patterns Book - by Project Error Handling WG](https://github.com/rust-lang/project-error-handling/blob/master/error-design-patterns-book/src/SUMMARY.md)
- [The Rust Cookbook - Error Handling](https://rust-lang-nursery.github.io/rust-cookbook/error-handling.html)
- [Le ticket initial de l'intégration de la gestion des erreurs dans le projet](https://forge.p4pillon.org/P4Pillon/Krys4lide/issues/34)

View File

@ -1,14 +0,0 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
[lib]
name = "entity"
path = "src/lib.rs"
[dependencies]
sea-orm.workspace = true
[dev-dependencies]
sea-orm-cli.workspace = true

View File

@ -1,17 +0,0 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "debug")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub title: String,
pub text: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,5 +0,0 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
pub mod prelude;
pub mod debug;

View File

@ -1,3 +0,0 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
pub use super::debug::Entity as Debug;

View File

@ -1,2 +0,0 @@
mod entities;
pub use entities::*;

BIN
lib/libp4pillondebuglib.a Normal file

Binary file not shown.

BIN
lib/p4pillondebuglib.lib Normal file

Binary file not shown.

BIN
lib/p4pillondebuglib.o Normal file

Binary file not shown.

BIN
lib/p4pillondebuglib.obj Normal file

Binary file not shown.

BIN
lib/ssvw64.exp Normal file

Binary file not shown.

36
make.bat Normal file
View File

@ -0,0 +1,36 @@
@echo off
rem Set variables
set SRC_DIR=src
set LIB_DIR=lib
rem Create the %LIB_DIR% directory if it does not exist
if not exist %LIB_DIR% mkdir %LIB_DIR%
if "%1"=="/clean" (
goto clean
)
rem Configure the environment for Visual Studio
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
rem Build %SRC_DIR%\*.c files
cl /c /Fd:%LIB_DIR%\ /Fo:%LIB_DIR%\ /Iinclude\ /I%SRC_DIR%\ %SRC_DIR%\*.c
rem Link the object files into a static library
lib %LIB_DIR%\*.obj
rem Create a ssvw64.lib file from a ssvw64.def file
lib /def:%SRC_DIR%\ssvw64.def /out:%LIB_DIR%\ssvw64.lib /machine:x64
rem Build complete
pause
exit /b 0
:clean
del %LIB_DIR%\*.obj
del %LIB_DIR%\*.lib
del %LIB_DIR%\*.exp
rem Clean complete
pause
exit /b 0

32
makefile Normal file
View File

@ -0,0 +1,32 @@
# Detect the operating system
ifeq ($(OS),Windows_NT)
MKDIR = if not exist $(LIB_DIR) mkdir $(LIB_DIR)
RM = del
else
MKDIR = mkdir -p $(LIB_DIR)
RM = rm -f
endif
CC = gcc
AR = ar
CFLAGS = -Wall -fPIC
SRC_DIR = src
LIB_DIR = lib
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
OBJ_FILES = $(SRC_FILES:$(SRC_DIR)/%.c=$(LIB_DIR)/%.o)
STATIC_LIB = $(LIB_DIR)/libp4pillondebuglib.a
all: $(STATIC_LIB)
$(STATIC_LIB): $(OBJ_FILES)
$(MKDIR)
$(AR) rcs $@ $^
$(LIB_DIR)/%.o: $(SRC_DIR)/%.c
$(MKDIR)
$(CC) $(CFLAGS) -c $< -o $@
clean:
$(RM) $(OBJ_FILES) $(STATIC_LIB)
.PHONY: all clean

View File

@ -1,24 +0,0 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
[dev-dependencies]
sea-orm-cli.workspace = true
[dependencies.sea-orm-migration]
version = "1.0.0"
features = [
# `ASYNC_RUNTIME` and `DATABASE_DRIVER` are required to run migration using the cli
# They must be the same as the features in the `sea-orm` dependency in the `app` crate
"sqlx-sqlite", # `DATABASE_DRIVER` feature
"runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
]

View File

@ -1,41 +0,0 @@
# Running Migrator CLI
- Generate a new migration file
```sh
cargo run -- generate MIGRATION_NAME
```
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

View File

@ -1,12 +0,0 @@
pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_debug_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220101_000001_create_debug_table::Migration)]
}
}

View File

@ -1,35 +0,0 @@
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Debug::Table)
.if_not_exists()
.col(pk_auto(Debug::Id))
.col(string(Debug::Title))
.col(string(Debug::Text))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Debug::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum Debug {
Table,
Id,
Title,
Text,
}

View File

@ -1,6 +0,0 @@
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}

View File

View File

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

7
src/main.rs Normal file
View File

@ -0,0 +1,7 @@
mod p4pillondebuglib_demo;
mod ssvlib_demo;
fn main() {
p4pillondebuglib_demo::demo();
ssvlib_demo::demo();
}

57
src/p4pillondebuglib.c Normal file
View File

@ -0,0 +1,57 @@
// mylib.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* hello() {
return "Hello, World!";
}
char* helloName(const char *name) {
char *result = (char *)malloc(strlen(name) + 9);
if (result) {
sprintf(result, "Hello, %s!", name);
}
return result;
}
void helloPtr(char *result) {
sprintf(result, "Hello, World!");
}
void helloPtrPtr(char **result, size_t *size) {
const char *message = "Hello, World!";
*size = strlen(message) + 1; // +1 for null terminator
*result = (char *)malloc(*size);
if (*result) {
strcpy(*result, message);
}
}
void helloVoidPtrPtr(void **result, size_t *size) {
const char *message = "Hello, World!";
*size = strlen(message) + 1; // +1 for null terminator
*result = malloc(*size);
if (*result != NULL) {
strcpy((char *)*result, message);
}
}
int add(int a, int b) {
return a + b;
}
// Add function returning result in a pointer
void addPtr(int a, int b, int *result) {
*result = a + b;
}
void fillHexValues(void **result, size_t *size) {
unsigned char values[5] = {0x05, 0xE7, 0x02, 0x00, 0x00};
*size = sizeof(values);
*result = malloc(*size);
if (*result != NULL) {
memcpy(*result, values, *size);
}
}

Some files were not shown because too many files have changed in this diff Show More