16 Commits

Author SHA1 Message Date
07eae87855 WIP 2024-09-24 21:06:14 +02:00
080537fe6d feat: define structure 2024-09-24 21:06:09 +02:00
ecd84bf242 feat: add init and close library 2024-09-24 21:05:00 +02:00
981a5d34c5 feat: add first draft for crate public api 2024-09-24 21:05:00 +02:00
681cc7cb83 chore: add comment to indicate bindgen 2024-09-24 21:05:00 +02:00
fbc3be564f feat: implement parsing to data structures 2024-09-24 21:05:00 +02:00
4871187726 chore: suppress dead code warning in bindings 2024-09-24 21:05:00 +02:00
67b482ff13 feat: implement deku binary reading 2024-09-24 21:05:00 +02:00
34bdea2269 WIP commit to set working base 2024-09-24 21:04:25 +02:00
20e16f6ae2 feat: test different implementations to parse memory 2024-09-24 21:04:25 +02:00
f42e38228a chore: start implementing types from docs 2024-09-24 21:04:20 +02:00
3712667a04 chore: init sys crate 2024-09-24 21:03:14 +02:00
345190dfeb Merge pull request 'Ajout d'un parcours utilisateur⋅ice de connexion / déconnexion' (#67) from feat/61_add_login_workflow into main
Reviewed-on: P4Pillon/Krys4lide#67
Reviewed-by: kosssi <simon@p4pillon.org>

### Détails

- Ajout d'une interface "de base", avec une navbar (supprimée par la #65)
- Ajout d'un bouton de connexion, ouvrant une modale
- Sélection de l'utilisateur⋅ice au click ou par raccourci clavier
- Usage réactif d'un état partagé entre les composants, pour stocker l'information de l'utilisateur⋅ice connecté⋅e
- Menu dropdown de "profil" & Déconnexion

![Peek 24-09-2024 01-03](/attachments/4ceda5b3-26d9-4022-8923-e65a08da8dcd)

# Compatibilité

Les choix d'implémentation des éléments "dynamiques" de l'interface (modale, dropdown), encouragés par la documentation de DaisyUI, s'appuient sur les dernières évolutions de la norme HTML : il n'y a donc aucun javascript pour les gérer, c'est fait nativement par le navigateur.

Il faudrait vérifier si les librairies et framework qu'on utilise implémentent ces fonctionnements en "polyfill" pour les anciens navigateurs. Si ce n'est pas le cas, il faudra définir si :
- on cherche des polyfills adaptés
- on laisse comme ça sans rétro-compatibilité (pas très "numérique responsable")
- on fallback sur des implémentations plus "traditionnelles" mais rétro-compatibles

Closes #61
2024-09-24 12:57:34 +02:00
5712d898a5 feat: Add a client-side only user selection interface 2024-09-24 12:56:50 +02:00
3bd0a02b62 feat: implement a simple navbar 2024-09-24 12:56:50 +02:00
167a1fbbc2 Merge pull request 'Refactoring de l'interface : migration d'un monolithe HTMx vers un client Nuxt + serveur Axum' (#66) from feat/65_move_out_htmx_with_axum_backend_and_nuxt_frontend into main
Reviewed-on: P4Pillon/Krys4lide#66
Reviewed-by: kosssi <simon@p4pillon.org>

Implémentation des réflexions menées dans #65 :
- Suppression de la crate `app`, qui était un serveur axum exposant du HTMx, embedded dans le Tauri de la crate `desktop`
- Création d'un module Typescript, `frontend`, basé sur NuxtJS et générant une application statique
- Ré-écriture complète de la crate `desktop` pour encapsuler l'application statique générée par `frontend`
- Création d'une crate `backend`, serveur axum ayant pour objectif de servir de backend à l'interface, en particulier pour centraliser les accès à une base de donnée unique

- J'ai ré-utilisé TailwindCSS, mais au travers du module Nuxt dédié ; la génération est donc propre et automatisée, sans même nécessiter de configuration
- J'ai rajouté une "surcouche" à Tailwind, DaisyUI plutôt que de re-partir sur Flowbite ; ça fournit un ensemble de composants, mais de manière moins intrusive et "opinionated"

cf #65

- [Nuxt](https://nuxt.com/)
- [DaisyUI](https://daisyui.com/)
- [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/)
2024-09-24 12:53:47 +02:00
8 changed files with 695 additions and 0 deletions

View File

@ -4,5 +4,6 @@ members = [
"crates/backend",
"crates/desktop",
"crates/sesam-vitale",
"crates/services-sesam-vitale-sys",
"crates/utils",
]

View File

@ -0,0 +1,12 @@
[package]
name = "services-sesam-vitale-sys"
version = "0.1.0"
edition = "2021"
#links= "ssvlux64"
[dependencies]
bitvec = "1.0.1"
deku = "0.17.0"
libc = "0.2.155"
num_enum = { version = "0.7.3", features = ["complex-expressions"] }
thiserror = "1.0.63"

View File

@ -0,0 +1,104 @@
use deku::{deku_derive, DekuContainerRead, DekuError, DekuReader};
use std::{ffi::CString, fmt, path::Path, ptr};
use thiserror::Error;
use crate::{
bindings::{SSV_InitLIB2, SSV_LireConfig, SSV_TermLIB},
types::{common::read_from_buffer, configuration::Configuration},
};
use num_enum::FromPrimitive;
#[derive(Error, Debug)]
pub struct SesamVitaleError {
code: u16,
}
#[derive(Debug, Eq, PartialEq, FromPrimitive)]
#[repr(u16)]
enum SSVIntError {
CPSNotInserted = 61441,
#[num_enum(catch_all)]
NotImplemented(u16),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sesam_vitale_error() {
let int_error = SSVIntError::from(61441);
assert_eq!(int_error, SSVIntError::CPSNotInserted);
let int_error = SSVIntError::from(123);
assert_eq!(int_error, SSVIntError::NotImplemented(123));
println!("{:?}", int_error);
}
}
#[derive(Error, Debug)]
enum SSVError {
#[error("Erreur standard de la librairie SSV")]
SSVStandard,
// #[error("Erreur de parsing")]
// Parsing(#[from] ParsingError),
#[error("Erreur inattendue de la librairie SSV (TMP)")]
SSVUnknownTmp,
}
impl fmt::Display for SesamVitaleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Got error code {} from SSV_LireConfig", self.code)
}
}
pub fn init_library(sesam_ini_path: &Path) -> Result<(), SesamVitaleError> {
// TODO: better error handling
let path_str = sesam_ini_path.to_str().unwrap();
let path_ptr = CString::new(path_str).expect("failed to create cstring");
let exit_code: u16 = unsafe { SSV_InitLIB2(path_ptr.as_ptr()) };
if exit_code != 0 {
let error = SesamVitaleError { code: exit_code };
return Err(error);
};
Ok(())
}
pub fn close_library() -> Result<(), SesamVitaleError> {
let exit_code: u16 = unsafe { SSV_TermLIB() };
if exit_code != 0 {
let error = SesamVitaleError { code: exit_code };
return Err(error);
};
Ok(())
}
pub fn read_config() -> Result<Configuration, SesamVitaleError> {
let mut buffer_ptr: *mut libc::c_void = ptr::null_mut();
let mut size: libc::size_t = 0;
let buffer_ptr_ptr: *mut *mut libc::c_void = &mut buffer_ptr;
let size_ptr: *mut libc::size_t = &mut size;
// Need to add proper error handling -> return a result with error code pointing to an error
// enum
let exit_code: u16 = unsafe { SSV_LireConfig(buffer_ptr_ptr, size_ptr) };
if exit_code != 0 {
let error = SesamVitaleError { code: exit_code };
return Err(error);
};
let buffer: &[u8] = unsafe { std::slice::from_raw_parts(buffer_ptr as *const u8, size) };
// TODO: Improve error handling
let configuration: Configuration = read_from_buffer(buffer).unwrap();
// TODO: Call library function for memory delocating
unsafe { libc::free(buffer_ptr) };
Ok(configuration)
}

View File

@ -0,0 +1,288 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
// Generated using bindgen
extern "C" {
// Fonctions de gestion des données
pub fn SSV_LireCartePS(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_LireDroitsVitale(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
DateConsultation: *const ::std::os::raw::c_char,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_FormaterFactures(
cFactureACreer: ::std::os::raw::c_char,
cModeSecur: ::std::os::raw::c_char,
cTypeFlux: ::std::os::raw::c_char,
pZDataIn: *mut ::std::os::raw::c_void,
TailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_ChiffrerFacture(
pZDataIn: *mut ::std::os::raw::c_void,
TailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_SignerFactureVitale(
pcNomRessourceVitale: *const ::std::os::raw::c_char,
pZDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_CalculerHashFactureAssure(
pcNumSerie: *const ::std::os::raw::c_char,
pZDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_AjouterSignatureAssureDansFacture(
pZDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_SignerFactureCPS(
pcNomRessourcePS: *const ::std::os::raw::c_char,
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
pcCodePorteurPS: *const ::std::os::raw::c_char,
cNologSituation: ::std::os::raw::c_char,
pZDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_FormaterLot(
NBZDataIn: ::std::os::raw::c_short,
TZDataIn: *mut *mut ::std::os::raw::c_void,
TTailleZoneIn: *mut usize,
pNbZDataOut: *mut ::std::os::raw::c_short,
TZDataOut: *mut *mut ::std::os::raw::c_void,
TTailleZoneOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_SignerLotCPS(
pcNomRessourcePS: *const ::std::os::raw::c_char,
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
pcCodePorteurPS: *const ::std::os::raw::c_char,
cNologSituation: ::std::os::raw::c_char,
pZDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_FormaterFichier(
pZDataIn: *mut ::std::os::raw::c_void,
TailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_TraduireARL(
NbZDonneesEntree: ::std::os::raw::c_short,
TZDataIn: *mut *mut ::std::os::raw::c_void,
TTailleZoneIn: *mut usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZoneOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_LireNumSerieCarteVitale(
pcNomRessource: *mut ::std::os::raw::c_char,
numeroSerie: *mut ::std::os::raw::c_uchar,
) -> ::std::os::raw::c_ushort;
pub fn SSV_CalculerHashFacturePS(
pcNumSerieCPS: *const ::std::os::raw::c_char,
pZDataIn: *mut ::std::os::raw::c_void,
usTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pusTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_AjouterSignaturePSFacture(
pZDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_DechargerFacturesPdT(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
pcNumFact: *const ::std::os::raw::c_char,
sNbZDataIn: ::std::os::raw::c_short,
pvTZDataIn: *mut *mut ::std::os::raw::c_void,
psTTailleDataIn: *mut usize,
pNbZDataOut: *mut ::std::os::raw::c_short,
TZDataOut: *mut *mut ::std::os::raw::c_void,
TTailleZoneOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_TraduireFSE(
pZDataIn: *mut ::std::os::raw::c_void,
TailleDataIn: usize,
pZDataOut: *mut *mut ::std::os::raw::c_void,
pTailleZone: *mut usize,
) -> ::std::os::raw::c_ushort;
// Fonctions TLA
// TLA (Terminal Lecteur Applicatif) -> lecteur autre que PC-SC, on ne prend pas en compte cela
pub fn SSV_IdentifierTLA(
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
NumVersionCDC: *const ::std::os::raw::c_char,
pZDataOut: *mut *mut ::std::os::raw::c_void,
tailleDataOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_ChargerDonneesTLA(
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
sNbZDataIn: ::std::os::raw::c_short,
pvTZDataIn: *mut *mut ::std::os::raw::c_void,
psTTailleDataIn: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_ChargerFacturesPdT(
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
pcNumFacturation: *const ::std::os::raw::c_char,
sNbZDataIn: ::std::os::raw::c_short,
pvTZDataIn: *mut *mut ::std::os::raw::c_void,
psTTailleDataIn: *mut usize,
pNbZDataOut: *mut ::std::os::raw::c_short,
TZDataOut: *mut *mut ::std::os::raw::c_void,
TTailleZoneOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_DechargerFSETLA(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
pcNumFact: *const ::std::os::raw::c_char,
pNbZDataOut: *mut ::std::os::raw::c_short,
TZDataOut: *mut *mut ::std::os::raw::c_void,
TTailleZoneOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_DechargerFSETLANC(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
pcNumFact: *const ::std::os::raw::c_char,
pNbZDataOut: *mut ::std::os::raw::c_short,
TZDataOut: *mut *mut ::std::os::raw::c_void,
TTailleZoneOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_DechargerBeneficiaires(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
cNumFacturation: *const ::std::os::raw::c_char,
sNbZDataOut: *mut ::std::os::raw::c_short,
pTZDataOut: *mut *mut ::std::os::raw::c_void,
sTTailleDataOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_EffacerTLA(
NomRessourcePS: *const ::std::os::raw::c_char,
NomRessourceLecteur: *const ::std::os::raw::c_char,
CodePorteurPS: *const ::std::os::raw::c_char,
cNumFacturation: *const ::std::os::raw::c_char,
cTypeDonnee: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_ushort;
pub fn SSV_SecuriserFacture(
pcNomRessourcePS: *const ::std::os::raw::c_char,
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
pcCodePorteurPS: *const ::std::os::raw::c_char,
cNologSituation: ::std::os::raw::c_char,
pcNumFact: *const ::std::os::raw::c_char,
pvDataIn: *mut ::std::os::raw::c_void,
szTailleDataIn: usize,
pvDataOut: *mut *mut ::std::os::raw::c_void,
pszTailleDataOut: *mut usize,
) -> ::std::os::raw::c_ushort;
// Fonctions de gestion de configuration (GALSS)
pub fn SSV_LireConfig(
pZDataOut: *mut *mut ::std::os::raw::c_void,
psTailleDataOut: *mut usize,
) -> ::std::os::raw::c_ushort;
pub fn SSV_LireDateLecteur(
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
pcDateHeure: *mut ::std::os::raw::c_char,
) -> ::std::os::raw::c_ushort;
pub fn SSV_MajDateLecteur(
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
pcDateHeure: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_ushort;
pub fn SSV_ChargerAppli(
pcNomRessourceLecteur: *const ::std::os::raw::c_char,
sNbZDataIn: ::std::os::raw::c_short,
pvTZDataIn: *mut *mut ::std::os::raw::c_void,
psTTailleDataIn: *mut usize,
) -> ::std::os::raw::c_ushort;
// Fonctions techniques
// La fonction Initialiser Librairie a pour objet de charger et dinitialiser dans la mémoire du système :
// - dans le cas où le GALSS est installé sur le poste :
// - la bibliothèque du Gestionnaire dAccès au Lecteur Santé Social (GALSS),
// - qui charge la bibliothèque du Protocole Santé Social (PSS),
// - la configuration du poste de travail à laide du fichier galssinf,
// - les variables globales communes aux différents Services SESAM-Vitale,
// - les fichiers de tables et scripts des répertoires par défaut.
// Cette fonction accède au référentiel électronique en utilisant le chemin complet indiqué dans le fichier sesam.ini.
pub fn SSV_InitLIB2(pcFichierSesam: *const ::std::os::raw::c_char) -> ::std::os::raw::c_ushort;
// La fonction Terminer a pour objet de décharger de la mémoire du système les éléments
// chargés par la fonction Initialiser Librairie, qui ne sont plus utiles.
pub fn SSV_TermLIB() -> ::std::os::raw::c_ushort;
/// Fonctions de Tracage
//La fonction Allouer Zone Mémoire a un rôle purement technique : elle permet dallouer, autrement dit de réserver une zone ou partie de la mémoire du poste de travail pour y écrire les données à passer en entrée dun Service SESAM-Vitale.
// Cette fonction doit être utilisée pour allouer toutes les zones de mémoire requises en entrée des Services SESAM-Vitale de manière à permettre un diagnostic fiable par le « mode trace » en cas de dysfonctionnement. En effet, son mode dexécution est susceptible de fournir des informations utiles au « mode trace » lorsquil est activé.
pub fn SSV_AllouerZoneMem(
pZDataIn: *mut *mut ::std::os::raw::c_void,
taille: usize,
) -> ::std::os::raw::c_ushort;
// La fonction Libérer Zone Mémoire a un rôle purement technique : elle permet de libérer une zone de mémoire du poste de travail précédemment allouée après exploitation des données quelle contient.
// Cette fonction doit être utilisée pour libérer toutes les zones de mémoire :
// - celles qui ont été allouées par le progiciel de santé pour fournir les données nécessaires à lentrée des Services SESAM-Vitale, avant leur appel, celles qui ont été allouées par les Services SESAM-Vitale pour fournir en sortie les données utiles au progiciel de santé qui a fait appel à ces services,
// - de façon à permettre un diagnostic fiable par le mode trace en cas de dysfonctionnement
//En effet, son exécution est susceptible de fournir des informations utiles au « mode trace » lorsquil est activé.
pub fn SSV_LibererZoneMem(pZone: *mut ::std::os::raw::c_void);
// La fonction Initialiser Trace a pour objet de permettre lactivation du « mode trace ».
// Ce mode de fonctionnement est prévu pour permettre à lassistance technique du GIE
// SESAM-Vitale danalyser les problèmes de mise en œuvre des Services SESAM-Vitale,
// notamment lorsque une fonction retourne un code derreur de valeur hexadécimale supérieure à FF00.
pub fn SSV_InitTrace(
pathConf: *mut ::std::os::raw::c_char,
ModeOuvertureFicherLog: *mut ::std::os::raw::c_char,
ModuleLog: ::std::os::raw::c_ushort,
NiveauLog: ::std::os::raw::c_uchar,
) -> ::std::os::raw::c_ushort;
}

View File

@ -0,0 +1,6 @@
pub mod api;
mod bindings;
pub mod types;
#[cfg(test)]
mod tests {}

View File

@ -0,0 +1,144 @@
use crate::types::configuration::{
ConfigurationHeader, PCSCReader, ReaderConfiguration, SESAMVitaleComponent,
};
use std::{error::Error, str::FromStr};
use bitvec::index::BitIdx;
use deku::{
bitvec::{BitStore, Msb0},
ctx::ByteSize,
deku_derive,
reader::{Reader, ReaderRet},
DekuContainerRead, DekuError, DekuReader,
};
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct NumericString(#[deku(map = "convert_from_data_field")] String);
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct AlphaNumericString(#[deku(map = "convert_from_data_field")] String);
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct BinaryData(#[deku(map = "extract_from_data_field")] Vec<u8>);
#[deku_derive(DekuRead)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[deku(endian = "big")]
pub(crate) struct GroupId(u16);
trait MapToDekuParseError<T> {
fn map_to_deku_parse_error(self) -> Result<T, DekuError>;
}
impl<T, E: Error> MapToDekuParseError<T> for Result<T, E> {
fn map_to_deku_parse_error(self) -> Result<T, DekuError> {
self.map_err(|e| DekuError::Parse(e.to_string().into()))
}
}
fn read_size<R: std::io::Read>(reader: &mut Reader<R>) -> Result<ByteSize, DekuError> {
let first_byte: u8 = u8::from_reader_with_ctx(reader, ())?;
let is_length_expanded = first_byte.get_bit::<Msb0>(BitIdx::new(0).map_to_deku_parse_error()?);
match is_length_expanded {
true => {
let size_of_data_size: ByteSize = ByteSize((first_byte & 0b0111_1111) as usize);
if size_of_data_size.0 > 4 {
return Err(DekuError::Parse("Size of the length encoding is > 4, this is not normal. Probable parsing error".to_string().into()));
};
// maximum size of the buffer is 4, we use the offset to read values less than 4 bytes
let buffer: &mut [u8; 4] = &mut [0; 4];
let write_offset = 4 - size_of_data_size.0;
match reader.read_bytes(size_of_data_size.0, &mut buffer[write_offset..])? {
ReaderRet::Bits(_bit_vec) => Err(DekuError::Parse("Got bits when trying to read bytes -> reader is unaligned, this is not normal.".to_string().into())),
ReaderRet::Bytes => Ok(ByteSize(u32::from_be_bytes(*buffer) as usize)),
}
}
false => Ok(ByteSize(first_byte as usize)),
}
}
// Using this as the map function asks deku to parse a datafield
// We then use the datafield and convert it to the corresponding value
pub(super) fn convert_from_data_field<T>(data_field: DataField) -> Result<T, DekuError>
where
T: FromStr,
T::Err: Error,
{
let text = String::from_utf8(data_field.data).map_to_deku_parse_error()?;
T::from_str(&text).map_to_deku_parse_error()
}
pub(crate) fn extract_from_data_field(data_field: DataField) -> Result<Vec<u8>, DekuError> {
Ok(data_field.data)
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub(crate) struct DataField {
#[deku(reader = "read_size(deku::reader)")]
pub(crate) data_size: ByteSize,
#[deku(bytes_read = "data_size.0")]
pub(crate) data: Vec<u8>,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub(crate) struct BlockHeader {
pub(crate) group_id: GroupId,
#[deku(reader = "read_size(deku::reader)")]
pub(crate) data_size: ByteSize,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub(crate) struct DataBlock {
pub(crate) header: BlockHeader,
#[deku(ctx = "header.group_id")]
pub(crate) inner: DataGroup,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
#[deku(ctx = "group_id: GroupId", id = "group_id.0")]
pub enum DataGroup {
#[deku(id = 60)]
ConfigurationHeader(ConfigurationHeader),
#[deku(id = 61)]
ReaderConfiguration(ReaderConfiguration),
#[deku(id = 64)]
SESAMVitaleComponent(SESAMVitaleComponent),
#[deku(id = 67)]
PCSCReader(PCSCReader),
}
pub(crate) fn read_from_buffer<T>(buffer: &[u8]) -> Result<T, T::Error>
where
T: TryFrom<Vec<DataBlock>>,
{
let mut data_blocks: Vec<DataBlock> = Vec::new();
let mut offset = 0;
let mut remaining_buffer = buffer;
while !remaining_buffer.is_empty() {
// TODO: properly handle errors
let (rest, data_block) = DataBlock::from_bytes((remaining_buffer, offset)).unwrap();
data_blocks.push(data_block);
(remaining_buffer, offset) = rest;
}
T::try_from(data_blocks)
}

View File

@ -0,0 +1,137 @@
use crate::types::common::DataBlock;
use std::{error::Error, fmt, vec::Vec};
use crate::types::common::convert_from_data_field;
use deku::{deku_derive, DekuReader};
use super::common::{AlphaNumericString, DataGroup};
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SSVVersionNumber(#[deku(map = "convert_from_data_field")] u16);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct GALSSVersionNumber(#[deku(map = "convert_from_data_field")] u16);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct PSSVersionNumber(#[deku(map = "convert_from_data_field")] u16);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ConfigurationHeader {
pub ssv_version: SSVVersionNumber,
pub galss_version: GALSSVersionNumber,
pub pss_version: PSSVersionNumber,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct PCSCReaderName(AlphaNumericString);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct CardType(#[deku(map = "convert_from_data_field")] u8);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct PCSCReader {
pub name: PCSCReaderName,
pub card_type: CardType,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SESAMVitaleComponentID(#[deku(map = "convert_from_data_field")] u16);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SESAMVitaleComponentDescription(AlphaNumericString);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SESAMVitaleComponentVersion(AlphaNumericString);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SESAMVitaleComponent {
pub id: SESAMVitaleComponentID,
pub description: SESAMVitaleComponentDescription,
pub version: SESAMVitaleComponentVersion,
}
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct ReaderConfiguration {}
#[derive(Debug)]
pub enum ConfigurationError {
MultipleConfigurationHeaders,
MissingConfigurationHeader,
}
impl fmt::Display for ConfigurationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigurationError::MultipleConfigurationHeaders => {
write!(f, "Multiple ConfigurationHeader blocks found")
}
ConfigurationError::MissingConfigurationHeader => {
write!(f, "Missing ConfigurationHeader block")
}
}
}
}
impl Error for ConfigurationError {}
#[derive(Debug)]
pub struct Configuration {
pub configuration_header: ConfigurationHeader,
pub reader_configurations: Vec<ReaderConfiguration>,
pub sesam_vitale_components: Vec<SESAMVitaleComponent>,
pub pcsc_readers: Vec<PCSCReader>,
}
impl TryFrom<Vec<DataBlock>> for Configuration {
type Error = ConfigurationError;
fn try_from(data_blocks: Vec<DataBlock>) -> Result<Self, Self::Error> {
let mut configuration_header: Option<ConfigurationHeader> = None;
let mut reader_configurations: Vec<ReaderConfiguration> = Vec::new();
let mut sesam_vitale_components: Vec<SESAMVitaleComponent> = Vec::new();
let mut pcsc_readers: Vec<PCSCReader> = Vec::new();
for block in data_blocks {
match block.inner {
DataGroup::ConfigurationHeader(header) => {
if configuration_header.is_some() {
return Err(ConfigurationError::MultipleConfigurationHeaders);
}
configuration_header = Some(header);
}
DataGroup::ReaderConfiguration(configuration) => {
reader_configurations.push(configuration)
}
DataGroup::SESAMVitaleComponent(component) => {
sesam_vitale_components.push(component);
}
DataGroup::PCSCReader(reader) => {
pcsc_readers.push(reader);
}
}
}
let configuration_header = match configuration_header {
Some(header) => header,
None => return Err(ConfigurationError::MissingConfigurationHeader),
};
Ok(Self {
configuration_header,
reader_configurations,
sesam_vitale_components,
pcsc_readers,
})
}
}

View File

@ -0,0 +1,3 @@
pub mod common;
pub mod configuration;
// pub mod droits_vitale;