Compare commits

...

32 Commits

Author SHA1 Message Date
952c8561ba
feat: add init and close library 2024-08-29 01:15:58 +02:00
9829a189a4
feat: add first draft for crate public api 2024-08-29 00:47:53 +02:00
c8f14cd77b
chore: add comment to indicate bindgen 2024-08-29 00:46:38 +02:00
6d9cd7fc14
feat: implement parsing to data structures 2024-08-28 23:34:09 +02:00
c0bbdcf030
chore: suppress dead code warning in bindings 2024-08-28 21:44:29 +02:00
488f719919
feat: implement deku binary reading 2024-08-28 21:44:08 +02:00
9b279ce4cd
chore: fmt cargo.toml 2024-08-28 21:41:29 +02:00
334e6520d5
WIP commit to set working base 2024-08-10 12:10:21 +02:00
68376383fa
refacto: add alpinejs, flowbite and htmx to app assets with explicit versions 2024-08-07 22:02:59 +02:00
581bd2455e
feat: add page system behing navbar items and skeletons for loading 2024-08-07 22:02:59 +02:00
37ef6e2c15
fixup! feat: make Nav and Profile menu dynamic 2024-08-07 22:02:59 +02:00
1d5553a739
fixup! feat: Setup Tailwind CSS 2024-08-07 22:02:59 +02:00
eca3ea2cd6
feat: make Nav and Profile menu dynamic 2024-08-07 22:02:59 +02:00
241629c7ab
feat: replace vanilla JS by AlpineJS 2024-08-07 22:02:59 +02:00
b9ac1a3587
feat: add navbar layout with some JS controls (with Alpine.js) 2024-08-07 22:02:59 +02:00
6209c087c2
refacto: nest axum templates routes 2024-08-07 22:02:59 +02:00
cba1534fd7
feat: Setup Tailwind CSS 2024-08-07 22:02:59 +02:00
Simon C
e03abbb87e
docs: Add Tauri version 2024-08-07 22:02:59 +02:00
e6db84e9ac
fix: handle multiple situations on a CPS 2024-08-07 22:02:59 +02:00
13254228a6
feat: add a field.id 2024-08-07 22:02:59 +02:00
f083c696f8
feature: handle some errors in decode_carte_ps 2024-08-07 22:02:59 +02:00
8b4fe4fd10
refactor: clean comments and docstring 2024-08-07 22:02:59 +02:00
160dcc9249
feat: cleaning of all the ssv_memory and ssvlib_demo to get a cleaner cps::lire_carte API 2024-08-07 22:02:59 +02:00
lienjukaisim
016ae43402
feat: implement some more structured version of the memory decoding and the mapping to CPS fields 2024-08-07 22:02:58 +02:00
01d17207fa
feat: implement block and fields as Struct implementing From trait 2024-08-07 22:02:58 +02:00
c82d3abd9c
feat: add read_element function for raw bloc or field parsing 2024-08-07 22:02:58 +02:00
cde7f3aab4
feat: add a function to read a "bloc / field size" in SSV memory 2024-08-07 22:02:58 +02:00
lienjukaisim
3a263f92e1
feat: add the structure to reprensent CPS fields returned by the lire_carte_ps function 2024-08-07 22:02:58 +02:00
13b92aee9c
feat: Implement a first version of the decode_zone_memoire function 2024-08-07 22:02:58 +02:00
3999532714
feat: test different implementations to parse memory 2024-08-04 23:02:12 +02:00
e51dcc0ed0
chore: start implementing types from docs 2024-08-04 01:24:42 +02:00
0f7a291b3c
chore: init sys crate 2024-08-03 17:23:23 +02:00
57 changed files with 3747 additions and 170 deletions

364
Cargo.lock generated
View File

@ -56,6 +56,55 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
@ -69,6 +118,7 @@ dependencies = [
"askama",
"askama_axum",
"axum",
"serde",
"tokio",
"tower-http",
]
@ -274,6 +324,18 @@ dependencies = [
"serde",
]
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block"
version = "0.1.6"
@ -327,9 +389,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.16.1"
version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83"
[[package]]
name = "byteorder"
@ -339,9 +401,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.6.1"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
dependencies = [
"serde",
]
@ -410,14 +472,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
dependencies = [
"serde",
"toml 0.8.16",
"toml 0.8.19",
]
[[package]]
name = "cc"
version = "1.1.6"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549"
[[package]]
name = "cesu8"
@ -501,6 +563,12 @@ dependencies = [
"objc",
]
[[package]]
name = "colorchoice"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "combine"
version = "4.6.7"
@ -672,6 +740,31 @@ dependencies = [
"syn 2.0.72",
]
[[package]]
name = "deku"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "709ade444d53896e60f6265660eb50480dd08b77bfc822e5dcc233b88b0b2fba"
dependencies = [
"bitvec",
"deku_derive",
"no_std_io",
"rustversion",
]
[[package]]
name = "deku_derive"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7534973f93f9de83203e41c8ddd32d230599fa73fa889f3deb1580ccd186913"
dependencies = [
"darling",
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -801,9 +894,9 @@ dependencies = [
[[package]]
name = "dunce"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "dyn-clone"
@ -820,7 +913,7 @@ dependencies = [
"cc",
"memchr",
"rustc_version",
"toml 0.8.16",
"toml 0.8.19",
"vswhom",
"winreg",
]
@ -831,6 +924,29 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "env_filter"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -868,9 +984,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.30"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -918,6 +1034,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futf"
version = "0.1.5"
@ -1418,6 +1540,12 @@ dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "1.4.1"
@ -1440,9 +1568,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956"
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
dependencies = [
"bytes",
"futures-channel",
@ -1520,9 +1648,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
dependencies = [
"equivalent",
"hashbrown 0.14.5",
@ -1553,6 +1681,12 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "0.4.8"
@ -1910,6 +2044,15 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "no_std_io"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fa5f306a6f2c01b4fd172f29bb46195b1764061bf926c75e96ff55df3178208"
dependencies = [
"memchr",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@ -2102,9 +2245,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.2"
version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e"
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
dependencies = [
"memchr",
]
@ -2360,7 +2503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [
"base64 0.22.1",
"indexmap 2.2.6",
"indexmap 2.3.0",
"quick-xml",
"serde",
"time",
@ -2387,9 +2530,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "precomputed-hash"
@ -2416,6 +2562,15 @@ dependencies = [
"toml_edit 0.20.7",
]
[[package]]
name = "proc-macro-crate"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
dependencies = [
"toml_edit 0.21.1",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -2473,6 +2628,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.7.3"
@ -2588,9 +2749,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.5"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -2814,11 +2975,12 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.120"
version = "1.0.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da"
dependencies = [
"itoa 1.0.11",
"memchr",
"ryu",
"serde",
]
@ -2875,7 +3037,7 @@ dependencies = [
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.2.6",
"indexmap 2.3.0",
"serde",
"serde_derive",
"serde_json",
@ -2917,6 +3079,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "services-sesam-vitale-sys"
version = "0.1.0"
dependencies = [
"bitvec",
"deku",
"libc",
]
[[package]]
name = "servo_arc"
version = "0.1.1"
@ -2931,8 +3102,11 @@ dependencies = [
name = "sesam-vitale"
version = "0.1.0"
dependencies = [
"deku",
"dotenv",
"env_logger",
"libc",
"services-sesam-vitale-sys",
]
[[package]]
@ -3142,7 +3316,7 @@ dependencies = [
"cfg-expr",
"heck 0.5.0",
"pkg-config",
"toml 0.8.16",
"toml 0.8.19",
"version-compare",
]
@ -3197,16 +3371,22 @@ dependencies = [
]
[[package]]
name = "target-lexicon"
version = "0.12.15"
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.0.0-beta.24"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eab508aad4ae86e23865e294b20a7bb89bd7afea523897b7478329b841d4295"
checksum = "7e2200ca115a6812984431f07fb0daa00afcd68b09d3ca24941b470f79462aa6"
dependencies = [
"anyhow",
"bytes",
@ -3253,9 +3433,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-beta.19"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498f587026501e4bbc5d6273b63f8956b03c37b3d3b2027f9c756fcd468e9c62"
checksum = "be3ff85695ade2315c82a7c04ac8904b12c0cc9981187cf4cd38700a6c739bfd"
dependencies = [
"anyhow",
"cargo_toml",
@ -3269,15 +3449,15 @@ dependencies = [
"serde_json",
"tauri-utils",
"tauri-winres",
"toml 0.8.16",
"toml 0.8.19",
"walkdir",
]
[[package]]
name = "tauri-codegen"
version = "2.0.0-beta.19"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bbc731067e319ef60601bf5716d1e706ee9ae28e38c0587f7165c7d6824cdf"
checksum = "ba3751f726e0180dfe43e66d6a73fe891eb898a06118b59547228ce8d331a0df"
dependencies = [
"base64 0.22.1",
"brotli",
@ -3302,9 +3482,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.0.0-beta.19"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36b4a44346577ccde75a24c62405a4c3b4f7a3a76614ee6cf1ed14a0b756795c"
checksum = "b03b174fc38ac96701f57fa1a8cfcc0686b10d1112e1ed98e9788689745c61e2"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -3316,9 +3496,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.0.0-beta.20"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe978df03966febbebc608931dc2cf26ef94df70855a18b05f07134cf474de09"
checksum = "68b0586932e7fd72778fb1067c16b5edf0d0d23c3fe1a1d9a6d9b212e7ab8394"
dependencies = [
"dpi",
"gtk",
@ -3335,9 +3515,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.0.0-beta.20"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11e4d568f61095f507b3fc4254dfbfff3b20de2a1d66167ffca3f6d90b14db8f"
checksum = "0ebb9ec03b2418a29f56da626da9c1b00ce085effd48bc7444bd864d889fe7ae"
dependencies = [
"cocoa",
"gtk",
@ -3359,9 +3539,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-beta.19"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e20e51856f343c503892749b27d34042e6ca83a0369a12de3c5552d9874d04e8"
checksum = "a640df6551e1d47f3c05c5296aa6c8b41ffad0f6fdcd42e244c2eaec160cb428"
dependencies = [
"brotli",
"cargo_metadata",
@ -3386,7 +3566,7 @@ dependencies = [
"serde_with",
"swift-rs",
"thiserror",
"toml 0.8.16",
"toml 0.8.19",
"url",
"urlpattern",
"walkdir",
@ -3497,9 +3677,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.39.1"
version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [
"backtrace",
"bytes",
@ -3549,21 +3729,21 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.16"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.17",
"toml_edit 0.22.20",
]
[[package]]
name = "toml_datetime"
version = "0.6.7"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
@ -3574,7 +3754,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.2.6",
"indexmap 2.3.0",
"serde",
"serde_spanned",
"toml_datetime",
@ -3587,22 +3767,33 @@ version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
"indexmap 2.2.6",
"indexmap 2.3.0",
"toml_datetime",
"winnow 0.5.40",
]
[[package]]
name = "toml_edit"
version = "0.22.17"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [
"indexmap 2.2.6",
"indexmap 2.3.0",
"toml_datetime",
"winnow 0.5.40",
]
[[package]]
name = "toml_edit"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap 2.3.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.6.16",
"winnow 0.6.18",
]
[[package]]
@ -3866,6 +4057,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.10.0"
@ -4131,11 +4328,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -4255,6 +4452,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
@ -4453,9 +4659,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.6.16"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]
@ -4512,6 +4718,15 @@ dependencies = [
"x11-dl",
]
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "x11"
version = "2.21.0"
@ -4532,3 +4747,24 @@ dependencies = [
"once_cell",
"pkg-config",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
]

View File

@ -3,5 +3,6 @@ resolver = "2"
members = [
"crates/app",
"crates/sesam-vitale",
"crates/desktop"
"crates/desktop",
"crates/services-sesam-vitale-sys",
]

View File

@ -12,23 +12,40 @@ Logiciel de Pharmacie libre et open-source.
### 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
cargo install tauri-cli --version "^2.0.0-beta"
```
### Exécution de l'application cliente desktop
#### Tailwindcss CLI
Le CLI Tailwindcss est nécessaire pour la génération du fichier `crates/app/assets/css/style.css`.
La documentation d'installation est disponible sur le site officiel de Tailwindcss : https://tailwindcss.com/blog/standalone-cli
La version actuellement utilisée est la [`v3.4.7`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.7)
#### SESAM-Vitale
La crate `sesam-vitale` nécessite la présence des librairies dynamiques fournies par le package FSV et la CryptolibCPS. Les instructions d'installation sont disponibles dans le [README](crates/sesam-vitale/README.md) de la crate `sesam-vitale`.
### Lancement
Le logiciel dans sa globalité peut être lancé via la commande suivante :
```bash
cargo tauri dev
```
### Exécution du serveur web `app` en mode endpoint
/!\ Attention, le lancement du client `desktop` ne génère pas le fichier `crates/app/assets/css/style.css` automatiquement pour le moment. En cas de modification des interfaces web, il est donc nécessaire de procéder à sa génération comme indiqué dans le [README](crates/app/README.md) de la crate `app`.
```bash
cargo run --bin 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)
## Build

View File

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

View File

@ -7,6 +7,7 @@ edition = "2021"
askama = "0.12.1"
askama_axum = "0.4.0"
axum = "0.7.5"
serde = { version = "1.0.204", features = ["derive"] }
tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5.2", features = ["fs"] }

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

@ -0,0 +1,15 @@
## Pré-requis
- Récupérer le binaire TailwindCSS : https://tailwindcss.com/blog/standalone-cli
## Exécution
- Lancer tailwindcss en mode watch dans un terminal :
```bash
./tailwindcss -i css/input.css -o assets/css/style.css --watch
```
- Lancer le serveur web dans un autre terminal :
```bash
cargo run --bin app
```

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

View File

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

View File

@ -9,6 +9,7 @@ async fn main() {
let router = get_router(assets_path.as_path());
// TODO: select port based on available port (or ask in CLI)
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
let listener = tokio::net::TcpListener::bind("localhost:3000").await.unwrap();
println!("Listening on: http://{}", listener.local_addr().unwrap());
axum::serve(listener, router).await.unwrap();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
[package]
name = "services-sesam-vitale-sys"
version = "0.1.0"
edition = "2021"
[dependencies]
bitvec = "1.0.1"
deku = "0.17.0"
libc = "0.2.155"

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,70 @@
mod bindings;
pub mod types;
use bindings::{SSV_InitLIB2, SSV_LireConfig, SSV_TermLIB};
use std::{ffi::CString, fmt, path::Path, ptr};
use types::serialization_types::{read_from_buffer, Configuration};
#[derive(Debug)]
pub struct SesamVitaleError {
code: u16,
}
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)
}
#[cfg(test)]
mod tests {}

View File

@ -0,0 +1,264 @@
pub struct Identification<T> {
value: T,
// Key to check the validity of the value
// TODO: implement checking algorithm
key: u8,
}
pub type Byte = u8;
pub(crate) enum IdentificationNationale {
NumeroAdeli(String),
NumeroEmployeeDansStructure(IdentificationStructure, String),
NumeroDRASS(String),
NumeroRPPS(String),
/// N° Etudiant Médecin type ADELI sur 9 caractères (information transmise par lANS)
NumeroEtudiantMedecin(String),
}
pub(crate) enum TypeCarteProfessionnelSante {
/// Carte de Professionnel de Santé (CPS)
CarteDeProfessionnelSante,
/// Carte de Professionnel de Santé en Formation (CPF)
CarteDeProfessionnelSanteEnFormation,
/// Carte de Personnel d'Établissement de Santé (CDE/CPE)
CarteDePersonnelEtablissementSante,
/// Carte de Personnel Autorisé (CDA/CPA)
CarteDePersonnelAutorise,
/// Carte de Personne Morale
CarteDePersonneMorale,
}
pub(crate) enum CategorieCarteProfessionnelSante {
Reelle,
Test,
Demonstration,
}
pub(crate) enum CodeCivilite {
Adjudant,
Amiral,
Aspirant,
Aumônier,
Capitaine,
Cardinal,
Chanoine,
Colonel,
Commandant,
Commissaire,
Conseiller,
Directeur,
Docteur,
Douanier,
Epouxse, // Epoux(se)
Evêque,
Général,
Gouverneur,
Ingénieur,
Inspecteur,
Lieutenant,
Madame,
Mademoiselle,
Maître,
Maréchal,
Médecin,
Mesdames,
Mesdemoiselles,
Messieurs,
Monseigneur,
Monsieur,
NotreDame,
Pasteur,
Préfet,
Président,
Professeur,
Recteur,
Sergent,
SousPréfet,
Technicien,
Veuve,
}
pub(crate) enum IdentificationStructure {
NumeroAdeliCabinet(String),
NumeroFINESS(String),
NumeroSIREN(String),
NumeroSIRET(String),
NumeroRPPSCabinet(String),
}
pub(crate) enum ModeExercice {
LiberalExploitantCommercant, // Libéral, exploitant, commerçant
Salarie,
Remplacant,
Benevole,
}
pub(crate) enum StatutExercice {
// TAB-Statuts géré par lANS il faut trouver la donnee
PLACEHOLDER(u8),
}
pub(crate) enum SecteurActivite {
EtablissementPublicDeSanté,
HopitauxMilitaires,
EtablissementPrivePSPH, // Participant au Service Public Hospitalier
EtablissementPriveNonPSPH,
DispensaireDeSoins,
AutresStructuresDeSoinsRelevantDuServiceDeSanteDesArmees,
CabinetIndividuel,
CabinetDeGroupe,
ExerciceEnSociete,
SecteurPrivePHTempsPlein,
TransportSanitaire,
EntrepriseDInterim,
EtablissementDeSoinsEtPrevention,
PreventionEtSoinsEnEntreprise,
SanteScolaireEtUniversitaire,
RecrutementEtGestionRH,
PMIPlanificationFamiliale,
EtablissementPourHandicapes,
ComMarketingConsultingMedia,
EtablissementPersonnesAgees,
EtablissementAideaLaFamille,
EtablissementDEnseignement,
EtablissementsDeProtectionDeLEnfance,
EtablissementsDHebergementEtDeReadaptation,
Recherche,
AssurancePrivee,
OrganismeDeSecuriteSociale,
MinistèreEtServicesDeconcentres,
CollectivitesTerritoriales,
AssociationsEtOrganitationsHumanitaire,
LaboratoireDeBiologieMedicale,
AutreEtablissementSanitaire,
ProductionCommercialisationGrosBienMedicaux,
CommerceDétailDeBiensMédicaux,
PharmacieDOfficine,
CentreDeDialyse,
ParaPharmacie,
AutreSecteurDActivité,
SecteurNonDefini,
CentreAntiCancer,
CentreDeTransfusionSanguine,
RépartitionDistribributionFabricationExploitationImportationMedicamentsEtDispositifsMédicaux,
IncendiesEtSecours,
EntreprisesIndustriellesEtTertiairesHorsIndustriesPharmaceutiques,
EntiteDUnTOM,
FabricationExploitationImportationMedicamentsEtDispositifsMedicaux,
}
pub(crate) type IdentificationFacturation = u32;
pub(crate) enum CodeConventionnel {
NonConventionne,
Conventionne,
ConventionneAvecDepassement,
ConventionneAvecHonorairesLibres,
}
/// Code spécialité ou Code spécialité de l'exécutant
pub(crate) enum CodeSpecialite {
MedecineGenerale,
AnesthesieReanimation,
Cardiologie,
ChirurgieGenerale,
DermatologieEtVenerologie,
Radiologie,
GynecologieObstetrique,
GastroEnterologieEtHepatologie,
MedecineInterne,
NeuroChirurgie,
OtoRhinoLaryngologie,
Pediatrie,
Pneumologie,
Rhumatologie,
Ophtalmologie,
ChirurgieUrologique,
NeuroPsychiatrie,
Stomatologie,
ChirurgienDentiste,
ReanimationMedicale,
SageFemme,
SpecialisteEnMedecineGeneraleAvecDiplome,
SpecialisteEnMedecineGeneraleReconnuParLOrdre,
Infirmier,
Psychologue,
MasseurKinesitherapeute,
PedicurePodologue,
Orthophoniste,
Orthoptiste,
LaboratoireDAnalysesMedicales,
ReeducationReadaptationFonctionnelle,
Neurologie,
Psychiatrie,
Geriatrie,
Nephrologie,
ChirurgieDentaireSpecialiteODF,
AnatomoCytoPathologie,
MedecinBiologiste,
LaboratoirePolyvalent,
LaboratoireDAnatomoCytoPathologique,
ChirurgieOrthopediqueEtTraumatologie,
EndocrinologieEtMetabolisme,
ChirurgieInfantile,
ChirurgieMaxilloFaciale,
ChirurgieMaxilloFacialeEtStomatologie,
ChirurgiePlastiqueReconstructriceEtEsthetique,
ChirurgieThoraciqueEtCardioVasculaire,
ChirurgieVasculaire,
ChirurgieVisceraleEtDigestive,
PharmacieDOfficine,
PharmacieMutualiste,
ChirurgienDentisteSpecialiteCO,
ChirurgienDentisteSpecialiteMBD,
PrestataireDeTypeSociete,
PrestataireArtisan,
PrestataireDeTypeAssociation,
Orthesiste,
Opticien,
Audioprothesiste,
ÉpithesisteOculariste,
PodoOrthesiste,
Orthoprothesiste,
ChirurgieOrale,
GynecologieMedicale,
Hematologie,
MedecineNucleaire,
OncologieMedicale,
OncologieRadiotherapique,
PsychiatrieDeLEnfantEtDeLAdolescent,
Radiotherapie,
Obstetrique,
GenetiqueMedicale,
ObstetriqueEtGynecologieMedicale,
SantePubliqueEtMedecineSociale,
MedecineDesMaladiesInfectieusesEtTropicales,
MedecineLegaleEtExpertisesMedicales,
MedecineDUrgence,
MedecineVasculaire,
Allergologie,
InfirmierExercantEnPratiquesAvancees, // IPA
}
/// Page 54 dictionnaires des donnees
/// donnees inutilises pour les pharmacies
pub(crate) enum CodeZoneTarifaire {}
pub(crate) enum CodeZoneIK {
PasIndemniteKilometrique,
IndemnitesKilometriquesPlaine,
IndemnitesKilometriquesMontagne,
}
pub(crate) enum CodeAgrement {
PasDAgrementRadio,
/// Agrément D ou agrément DDASS
AgrementDDASS,
/// Agrément A, B, C, E et F
AgrementABCEF,
/// Agrément G, H et J
AgrementGHJ,
AgrementK,
AgrementL,
AgrementM,
}

View File

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

View File

@ -0,0 +1,248 @@
use bitvec::index::BitIdx;
use std::{error::Error, fmt, str::FromStr, vec::Vec};
use deku::{
bitvec::{BitStore, Msb0}, ctx::ByteSize, deku_derive, reader::{Reader, ReaderRet}, DekuContainerRead, DekuError, DekuReader
};
#[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()))
}
}
#[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,
}
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
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()
}
#[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(#[deku(map = "convert_from_data_field")] String);
#[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(#[deku(map = "convert_from_data_field")] String);
#[deku_derive(DekuRead)]
#[derive(Debug, PartialEq)]
pub struct SESAMVitaleComponentVersion(#[deku(map = "convert_from_data_field")] String);
#[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 {}
#[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),
}
#[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,
})
}
}
pub(crate) fn read_from_buffer<T: TryFrom<Vec<DataBlock>>>(buffer: &[u8]) -> Result<T, T::Error>{
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,45 @@
pub(crate) use crate::types::common::IdentificationNationale;
use super::common::{
Byte, CategorieCarteProfessionnelSante, CodeAgrement, CodeCivilite, CodeConventionnel,
CodeSpecialite, CodeZoneIK, CodeZoneTarifaire, Identification, IdentificationFacturation,
IdentificationStructure, ModeExercice, SecteurActivite, StatutExercice,
TypeCarteProfessionnelSante,
};
pub(crate) struct CarteProfessionnelSante {
type_carte: TypeCarteProfessionnelSante,
categorie_carte: CategorieCarteProfessionnelSante,
professionnel_sante: ProfessionnelDeSante,
}
struct ProfessionnelDeSante {
prenom: String,
nom: String,
code_civilite: CodeCivilite,
identification_nationale: Identification<IdentificationNationale>,
situations_execice: Vec<SituationDExercice>,
}
struct StructureMedicale {
/// Nom Entreprise
raison_sociale: String,
identification: Identification<IdentificationStructure>,
}
struct SituationDExercice {
/// Numéro identifiant la situation du PS parmi ses autres situations inscrites sur sa CPS
identifiant_situation: Byte,
mode_exercice: Option<ModeExercice>,
statut_exercice: Option<StatutExercice>,
secteur_activite: Option<SecteurActivite>,
structure_d_exercice: Option<StructureMedicale>,
identification_facturation: Identification<IdentificationFacturation>,
identification_remplacant: Option<Identification<IdentificationNationale>>,
code_conventionnel: CodeConventionnel,
code_specialite: CodeSpecialite,
code_zone_tarifaire: CodeZoneTarifaire,
code_zone_ik: CodeZoneIK,
code_agrement: CodeAgrement,
habilite_signature_facture: bool,
habilite_signature_lot: bool,
}

View File

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

View File

@ -1,3 +1,8 @@
pub mod cps;
pub mod libssv;
pub mod ssv_memory;
pub mod ssvlib_demo;
pub fn add(left: usize, right: usize) -> usize {
left + right
}

View File

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

View File

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

View File

@ -0,0 +1,283 @@
/// # SSV Memory
/// Provide functions to manipulate raw memory from SSV library.
use std::convert::TryFrom;
#[derive(PartialEq, Debug)]
struct ElementSize {
pub size: usize,
pub pad: usize,
}
// TODO : Est-ce qu'on pourrait/devrait définir un type custom pour représenter les tableaux de bytes ?
impl TryFrom<&[u8]> for ElementSize {
type Error = &'static str;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.is_empty() {
return Err("Empty bytes input");
}
let mut element_size = ElementSize { size: 0, pad: 1 };
// Longueur:
// - si le bit de poids fort du premier octet est à 0, la longueur est codée sur un octet
// - si le bit de poids fort du premier octet est à 1, les 7 bits de poids faible codent le nombre d'octets utilisés pour coder la longueur
if bytes[0] & 0b1000_0000 == 0 {
// Size coded on 1 byte
element_size.size = bytes[0] as usize;
} else {
// Size coded on N bytes
// N are the 7 lower bits of the first byte
let size_bytes_len = (bytes[0] & 0b0111_1111) as usize;
if size_bytes_len > bytes.len() - 1 {
return Err("Invalid memory: not enough bytes to read the size");
} else if size_bytes_len > 4 {
return Err("Invalid memory: size is too big");
}
let size_bytes = &bytes[1..1 + size_bytes_len];
// u32::from_be_bytes() requires a 4 bytes array
let mut padded_bytes = [0u8; 4];
padded_bytes[size_bytes_len..].copy_from_slice(size_bytes);
element_size.size = u32::from_be_bytes(padded_bytes) as usize;
element_size.pad += size_bytes_len;
}
Ok(element_size)
}
}
#[derive(Debug)]
pub struct Block<'a> {
pub id: u16,
pub size: usize,
pub content: Vec<Field<'a>>,
}
impl<'a> From<&'a [u8]> for Block<'a> {
fn from(bytes: &'a [u8]) -> Self {
let mut offset = 0;
let id = u16::from_be_bytes(bytes[..2].try_into().unwrap());
offset += 2;
let ElementSize {
size: block_size,
pad,
} = bytes[2..].try_into().unwrap();
offset += pad;
let raw_content = &bytes[offset..];
let mut field_offset = 0;
// While there is still content to read, parse Fields
let mut content = Vec::new();
let mut field_id = 1;
while field_offset < block_size {
let mut field: Field<'a> = raw_content[field_offset..].into();
field.id = field_id;
field_offset += field.size;
field_id += 1;
content.push(field);
}
Block {
id,
size: offset + block_size,
content,
}
}
}
#[derive(Debug)]
pub struct Field<'a> {
pub id: u16,
pub size: usize,
pub content: &'a [u8],
}
impl<'a> From<&'a [u8]> for Field<'a> {
fn from(bytes: &'a [u8]) -> Self {
let ElementSize { size, pad } = bytes.try_into().unwrap();
let contenu = &bytes[pad..pad + size];
Field {
id: 0,
size: pad + size,
content: contenu,
}
}
}
pub fn decode_ssv_memory(bytes: &[u8], size: usize) -> Vec<Block> {
let mut blocks: Vec<Block> = Vec::new();
let mut offset = 0;
while offset < size {
let block: Block = bytes[offset..].into();
offset += block.size;
blocks.push(block);
}
blocks
}
#[cfg(test)]
mod test_element_size {
use super::*;
#[test]
fn short_size() {
let bytes: &[u8] = &[0b_0000_0001_u8];
let element_size: ElementSize = bytes.try_into().unwrap();
assert_eq!(element_size.size, 1);
assert_eq!(element_size.pad, 1);
let bytes: &[u8] = &[0b_0100_0000_u8];
let element_size: ElementSize = bytes.try_into().unwrap();
assert_eq!(element_size.size, 64);
assert_eq!(element_size.pad, 1);
}
#[test]
fn long_size() {
let bytes: &[u8] = &[0b_1000_0010_u8, 0b_0000_0001_u8, 0b_0100_0000_u8];
let element_size: ElementSize = bytes.try_into().unwrap();
assert_eq!(element_size.size, 320);
assert_eq!(element_size.pad, 3);
}
#[test]
fn null_size() {
let bytes: &[u8] = &[];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(result, Err("Empty bytes input"),);
}
#[test]
fn invalid_memory() {
let bytes: &[u8] = &[0b_1000_0001_u8];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(
result,
Err("Invalid memory: not enough bytes to read the size"),
);
let bytes: &[u8] = &[0b_1000_0010_u8, 1];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(
result,
Err("Invalid memory: not enough bytes to read the size"),
);
let bytes: &[u8] = &[0b_1000_0101_u8, 1, 1, 1, 1, 1];
let result: Result<ElementSize, &str> = bytes.try_into();
assert_eq!(result, Err("Invalid memory: size is too big"),);
}
}
#[cfg(test)]
mod test_field {
use super::*;
#[test]
fn short_size() {
let bytes: &[u8] = &[
51, 1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, 1, 52, 2, 50, 50, 17,
80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, 9, 70, 82, 65, 78,
67, 79, 73, 83, 69, 1, 84,
];
let element: Field = bytes.into();
assert_eq!(element.size, 52);
assert_eq!(element.content[..5], [1, 48, 1, 56, 11]);
}
#[test]
fn long_size() {
let mut bytes_vec = vec![
0b_1000_0010_u8,
0b_0000_0001_u8,
0b_0000_0000_u8, // size = 256
];
// Add 256 bytes to the content
bytes_vec.append(&mut vec![1; 256]);
let bytes: &[u8] = &bytes_vec;
let element: Field = bytes.into();
assert_eq!(element.size, 259);
assert_eq!(element.content.len(), 256);
}
}
#[cfg(test)]
mod test_block {
use super::*;
#[test]
fn test_francoise_pharmacien0052419_partial_block_1() {
let bytes: &[u8] = &[1, 48, 1, 56, 11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52];
let field1: Field = bytes.into();
assert_eq!(field1.size, 2);
assert_eq!(field1.content, &[48]);
let field2: Field = bytes[field1.size..].into();
assert_eq!(field2.size, 2);
assert_eq!(field2.content, &[56]);
let field3: Field = bytes[field1.size + field2.size..].into();
assert_eq!(field3.size, 12);
assert_eq!(
field3.content,
&[57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52]
);
}
#[test]
fn test_francoise_pharmacien0052419() {
let bytes: &[u8] = &[
0, 1, 51, // 3
1, 48, // 2
1, 56, // 2
11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12
1, 52, // 2
2, 50, 50, // 3
17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18
9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10
1, 84, // 2
// total: 54
0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56,
1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69,
50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53,
48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
];
let first_block: Block = bytes.into();
assert_eq!(first_block.id, 1);
assert_eq!(first_block.size, 54);
assert_eq!(first_block.content.len(), 8);
let second_block: Block = bytes[first_block.size..].into();
assert_eq!(second_block.id, 2);
assert_eq!(second_block.size, 86);
assert_eq!(second_block.content.len(), 21);
}
}
#[cfg(test)]
mod test_decode_ssv_memory {
use super::*;
#[test]
fn test_francoise_pharmacien0052419() {
let bytes: &[u8] = &[
0, 1, 51, // 3
1, 48, // 2
1, 56, // 2
11, 57, 57, 55, 48, 48, 53, 50, 52, 49, 57, 52, // 12
1, 52, // 2
2, 50, 50, // 3
17, 80, 72, 65, 82, 77, 65, 67, 73, 69, 78, 48, 48, 53, 50, 52, 49, 57, // 18
9, 70, 82, 65, 78, 67, 79, 73, 83, 69, // 10
1, 84, // 2
// total: 54
0, 2, 83, 1, 1, 1, 48, 1, 49, 2, 56, 54, 1, 49, 9, 48, 66, 48, 50, 50, 49, 57, 53, 56,
1, 56, 24, 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, 68, 85, 32, 67, 69, 78, 84, 82, 69,
50, 50, 49, 57, 53, 8, 48, 48, 50, 48, 50, 52, 49, 57, 1, 56, 0, 1, 48, 1, 49, 2, 53,
48, 2, 49, 48, 2, 48, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49,
];
let blocks = decode_ssv_memory(bytes, bytes.len());
assert_eq!(blocks.len(), 2);
}
}

View File

@ -1,24 +1,14 @@
/**
* High level API for the SSV library,
* based on the low level bindings in libssv.rs.
*
*/
extern crate libc;
/// High level API for the SSV library,
/// based on the low level bindings in libssv.rs.
extern crate dotenv;
use libc::{ c_void, size_t };
use libc::{c_void, size_t};
use std::env;
use std::ffi::CString;
use std::path::PathBuf;
use std::ptr;
use std::env;
use crate::libssv:: {
SSV_InitLIB2,
SSV_LireCartePS,
SSV_LireConfig
};
use crate::cps::lire_carte;
use crate::libssv::{SSV_InitLIB2, SSV_LireConfig};
fn ssv_init_lib_2() {
let ini_str = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set");
@ -29,35 +19,6 @@ fn ssv_init_lib_2() {
}
}
fn ssv_lire_carte_ps() {
let resource_ps = CString::new("PS").expect("CString::new failed");
let resource_reader = CString::new("TRANSPA1").expect("CString::new failed");
let card_number = CString::new("1234567890").expect("CString::new failed");
let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0;
unsafe {
let result = SSV_LireCartePS(
resource_ps.as_ptr(),
resource_reader.as_ptr(),
card_number.as_ptr(),
&mut buffer,
&mut size
);
println!("SSV_LireCartePS result: {}", result);
if !buffer.is_null() {
let hex_values = std::slice::from_raw_parts(buffer as *const u8, size);
for &byte in hex_values {
print!("{:02X} ", byte);
}
println!();
libc::free(buffer);
}
}
}
fn ssv_lire_config() {
let mut buffer: *mut c_void = ptr::null_mut();
let mut size: size_t = 0;
@ -78,10 +39,8 @@ fn ssv_lire_config() {
}
pub fn demo() {
/*
* TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env`
* Maybe we could use a system standard config path to store a config file
*/
// TODO : this is probably not working on release, because I'm not sure it exists a CARGO_MANIFEST_DIR and so it can find the `.env`
// Maybe we could use a system standard config path to store a config file
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let manifest_path = PathBuf::from(manifest_dir);
dotenv::from_path(manifest_path.join(".env")).ok();
@ -89,7 +48,12 @@ pub fn demo() {
println!("------- Demo for the SSV library --------");
ssv_init_lib_2();
ssv_lire_carte_ps();
let code_pin = "1234";
let lecteur = "HID Global OMNIKEY 3x21 Smart Card Reader 0";
let carte_ps = lire_carte(code_pin, lecteur).unwrap();
println!("CartePS: {:#?}", carte_ps);
ssv_lire_config();
println!("-----------------------------------------");