This commit is contained in:
2025-07-15 17:05:04 +02:00
commit d72254c7d0
25 changed files with 2074 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ox_speak_server.iml" filepath="$PROJECT_DIR$/.idea/ox_speak_server.iml" />
</modules>
</component>
</project>

11
.idea/ox_speak_server.iml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

639
Cargo.lock generated Normal file
View File

@@ -0,0 +1,639 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "event-listener"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "io-uring"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ox_speak_server"
version = "0.1.0"
dependencies = [
"bytes",
"dashmap",
"event-listener",
"parking_lot",
"serde",
"serde_json",
"strum",
"tokio",
"uuid",
]
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "redox_syscall"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "strum"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "uuid"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"getrandom",
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]

25
Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
name = "ox_speak_server"
version = "0.1.0"
edition = "2024"
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "ox_speak_server_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[profile.release]
debug = true
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
parking_lot = "0.12"
tokio = { version = "1.46", features = ["full"] }
strum = {version = "0.27", features = ["derive"] }
uuid = {version = "1.17", features = ["v4", "serde"] }
event-listener = "5.4"
dashmap = "6.1"
bytes = "1.10"

BIN
profile.json.gz Normal file

Binary file not shown.

66
src/app/app.rs Normal file
View File

@@ -0,0 +1,66 @@
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::time::interval;
use crate::domain::client::ClientManager;
use crate::domain::event::{Event, EventBus};
use crate::network::udp::UdpServer;
use crate::runtime::dispatcher::Dispatcher;
pub struct App {
// Communication inter-components
event_bus: EventBus,
dispatcher: Dispatcher,
event_rx: Option<mpsc::Receiver<Event>>,
// Network
udp_server: UdpServer,
// Clients
client_manager: ClientManager,
}
impl App {
pub async fn new() -> Self {
let (event_bus, event_rx) = EventBus::new();
let udp_server = UdpServer::new(event_bus.clone(), "127.0.0.1:5000").await;
let client_manager = ClientManager::new();
let dispatcher = Dispatcher::new(event_bus.clone(), udp_server.clone(), client_manager.clone()).await;
Self {
event_bus,
dispatcher,
event_rx: Some(event_rx),
udp_server,
client_manager
}
}
pub async fn start(&mut self) {
if let Some(event_rx) = self.event_rx.take() {
let dispatcher = self.dispatcher.clone();
tokio::spawn(async move {
dispatcher.start(event_rx).await;
});
}
let _ = self.udp_server.start().await;
let _ = self.tick_tasks().await;
println!("App started");
}
async fn tick_tasks(&self) {
let event_bus = self.event_bus.clone();
tokio::spawn(async move {
let mut interval = interval(Duration::from_secs(1));
loop {
// println!("Tick");
interval.tick().await;
let _ = event_bus.emit(Event::TickSeconds).await;
}
});
}
}

1
src/app/mod.rs Normal file
View File

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

0
src/core/mod.rs Normal file
View File

162
src/domain/client.rs Normal file
View File

@@ -0,0 +1,162 @@
//! Gestion des clients pour les connexions UDP
//!
//! Ce module fournit les structures et méthodes pour gérer les clients
//! connectés au serveur UDP, incluant leur tracking et leurs modifications.
use std::net::SocketAddr;
use std::sync::Arc;
use dashmap::DashMap;
use tokio::time::Instant;
use uuid::Uuid;
use std::hash::{Hash, Hasher};
use std::time::Duration;
/// Représente un client connecté au serveur UDP
///
/// Chaque client est identifié par un UUID unique et contient
/// son adresse réseau ainsi que l'heure de sa dernière activité.
#[derive(Debug)]
pub struct Client {
id: Uuid,
address: SocketAddr,
last_seen: Instant,
}
/// Gestionnaire threadsafe pour les clients connectés
///
/// Utilise `DashMap` pour permettre un accès concurrent sécurisé
/// aux clients depuis plusieurs threads.
#[derive(Clone)]
pub struct ClientManager {
clients: Arc<DashMap<SocketAddr, Client>>,
}
impl Client {
/// Crée un nouveau client avec un UUID généré automatiquement
pub fn new(address: SocketAddr) -> Self {
let id = Uuid::new_v4();
Self {
id,
address,
last_seen: Instant::now(),
}
}
/// Retourne le UUID unique du client
pub fn id(&self) -> Uuid {
self.id
}
/// Retourne l'adresse socket du client
pub fn address(&self) -> SocketAddr {
self.address
}
/// Retourne l'instant de la dernière activité du client
pub fn last_seen(&self) -> Instant {
self.last_seen
}
/// Met à jour l'heure de dernière activité du client à maintenant
pub fn update_last_seen(&mut self) {
self.last_seen = Instant::now();
}
}
impl Hash for Client {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for Client {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Client {}
impl ClientManager {
/// Crée un nouveau gestionnaire de clients vide
pub fn new() -> Self {
Self {
clients: Arc::new(DashMap::new()),
}
}
/// Ajoute un client au gestionnaire
pub fn add(&self, client: Client) {
self.clients.insert(client.address(), client);
}
/// Supprime un client du gestionnaire
pub fn remove(&self, client: Client) {
self.clients.remove(&client.address());
}
/// Vérifie si un client existe pour une adresse donnée
pub fn client_exists(&self, address: SocketAddr) -> bool {
self.clients.contains_key(&address)
}
/// Récupère une référence vers un client par son adresse
pub fn get_client_by_address(&self, address: SocketAddr) -> Option<dashmap::mapref::one::Ref<SocketAddr, Client>> {
self.clients.get(&address)
}
/// Récupère toutes les adresses des clients connectés
pub fn get_all_adresses(&self) -> Vec<SocketAddr> {
self.clients.iter().map(|entry| *entry.key()).collect()
}
/// Met à jour l'heure de dernière activité d'un client
pub fn update_client_last_seen(&self, address: SocketAddr) {
if let Some(mut client) = self.clients.get_mut(&address) {
client.update_last_seen();
}
}
/// Supprimer les clients trop vieux
pub fn cleanup(&self, max_age: Duration) {
let now = Instant::now();
self.clients.retain(|_, client| now - client.last_seen() < max_age);
}
/// Modifie un client via une closure
///
/// # Arguments
/// * `address` - L'adresse du client à modifier
/// * `f` - La closure qui recevra une référence mutable vers le client
///
/// # Returns
/// `true` si le client a été trouvé et modifié, `false` sinon
///
/// # Examples
/// ```ignore
/// let client_manager = ClientManager::new();
/// let addr = "127.0.0.1:8080".parse().unwrap();
///
/// // Mise à jour simple
/// client_manager.modify_client(addr, |client| {
/// client.update_last_seen();
/// });
///
/// // Modifications multiples
/// let success = client_manager.modify_client(addr, |client| {
/// client.update_last_seen();
/// // autres modifications...
/// });
/// ```
pub fn modify_client<F>(&self, address: SocketAddr, f: F) -> bool
where
F: FnOnce(&mut Client),
{
if let Some(mut client) = self.clients.get_mut(&address) {
f(&mut *client);
true
} else {
false
}
}
}

40
src/domain/event.rs Normal file
View File

@@ -0,0 +1,40 @@
use std::net::SocketAddr;
use tokio::sync::mpsc;
use crate::network::protocol::{UDPMessage};
#[derive(Clone, Debug)]
pub enum Event {
AppStarted,
AppStopped,
UdpStarted,
UdpStopped,
UdpIn(UDPMessage),
UdpOut(UDPMessage),
TickSeconds
}
#[derive(Clone)]
pub struct EventBus {
pub sender: mpsc::Sender<Event>,
}
impl EventBus {
pub fn new() -> (Self, mpsc::Receiver<Event>) {
let (sender, receiver) = mpsc::channel(10000);
(Self { sender }, receiver)
}
pub async fn emit(&self, event: Event) {
let _ = self.sender.send(event).await;
}
pub fn emit_sync(&self, event: Event) {
let _ = self.sender.try_send(event);
}
pub fn clone_sender(&self) -> mpsc::Sender<Event> {
self.sender.clone()
}
}

3
src/domain/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod event;
pub mod user;
pub mod client;

47
src/domain/user.rs Normal file
View File

@@ -0,0 +1,47 @@
use std::net::SocketAddr;
use std::sync::Arc;
use dashmap::DashMap;
use uuid::Uuid;
pub struct User {
id: Uuid,
udp_addr: Option<SocketAddr>,
}
#[derive(Clone)]
pub struct UserManager {
users: Arc<DashMap<Uuid, User>>,
}
impl User {
pub fn new(id: Uuid) -> Self {
Self {
id,
udp_addr: None,
}
}
pub fn default() -> Self {
Self::new(Uuid::new_v4())
}
pub fn set_udp_addr(&mut self, udp_addr: SocketAddr) {
self.udp_addr = Some(udp_addr);
}
}
impl UserManager {
pub fn new() -> Self {
Self {
users: Arc::new(DashMap::new())
}
}
pub fn add_user(&self, user: User) {
self.users.insert(user.id, user);
}
pub fn delete_user(&self, user: User) {
self.users.remove(&user.id);
}
}

6
src/lib.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod app;
pub mod core;
pub mod domain;
pub mod network;
pub mod runtime;
pub mod utils;

19
src/main.rs Normal file
View File

@@ -0,0 +1,19 @@
use tokio::signal;
use ox_speak_server_lib::app::app::App;
#[tokio::main]
async fn main() {
let mut app = App::new().await;
app.start().await;
// Attendre le signal Ctrl+C
match signal::ctrl_c().await {
Ok(()) => {
println!("Arrêt du serveur...");
}
Err(err) => {
eprintln!("Erreur lors de l'écoute du signal: {}", err);
}
}
}

2
src/network/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod protocol;
pub mod udp;

246
src/network/protocol.rs Normal file
View File

@@ -0,0 +1,246 @@
use std::collections::HashSet;
use bytes::{Bytes, BytesMut, Buf, BufMut};
use std::net::SocketAddr;
use uuid::Uuid;
use strum::{EnumIter, FromRepr};
#[repr(u8)]
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, EnumIter, FromRepr)]
pub enum UDPMessageType {
Ping = 0,
Audio = 1,
// Futurs types ici...
}
#[derive(Debug, Clone, PartialEq)]
pub enum UDPMessageData {
// Client messages - Zero-copy avec Bytes
ClientPing { message_id: Uuid },
ClientAudio { sequence: u16, data: Bytes },
// Server messages
ServerPing { message_id: Uuid },
ServerAudio { user: Uuid, sequence: u16, data: Bytes },
}
#[derive(Debug, Clone, PartialEq)]
pub struct UDPMessage {
pub data: UDPMessageData,
pub address: SocketAddr,
pub size: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UdpBroadcastMessage {
pub data: UDPMessageData,
pub addresses: HashSet<SocketAddr>, // ou Vec<SocketAddr> selon besoins
pub size: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseError {
EmptyData,
InvalidData,
InvalidMessageType,
InvalidUuid,
}
impl From<uuid::Error> for ParseError {
fn from(_: uuid::Error) -> Self {
ParseError::InvalidUuid
}
}
impl UDPMessageData {
// Parsing zero-copy depuis Bytes
pub fn from_client_bytes(mut data: Bytes) -> Result<Self, ParseError> {
if data.is_empty() {
return Err(ParseError::EmptyData);
}
let msg_type = data.get_u8(); // Consomme 1 byte
match msg_type {
0 => { // Ping
if data.remaining() < 16 {
return Err(ParseError::InvalidData);
}
let uuid_bytes = data.split_to(16); // Zero-copy split
let message_id = Uuid::from_slice(&uuid_bytes)?;
Ok(Self::ClientPing { message_id })
}
1 => { // Audio
if data.remaining() < 2 {
return Err(ParseError::InvalidData);
}
let sequence = data.get_u16(); // Big-endian par défaut
let audio_data = data; // Le reste pour l'audio
Ok(Self::ClientAudio { sequence, data: audio_data })
}
_ => Err(ParseError::InvalidMessageType),
}
}
// Constructeurs server
pub fn server_ping(message_id: Uuid) -> Self {
Self::ServerPing { message_id }
}
pub fn server_audio(user: Uuid, sequence: u16, data: Bytes) -> Self {
Self::ServerAudio { user, sequence, data }
}
// Sérialisation optimisée avec BytesMut
pub fn to_bytes(&self) -> Bytes {
match self {
Self::ServerPing { message_id } => {
let mut buf = BytesMut::with_capacity(17);
buf.put_u8(0); // Message type
buf.put_slice(message_id.as_bytes());
buf.freeze()
}
Self::ServerAudio { user, sequence, data } => {
let mut buf = BytesMut::with_capacity(19 + data.len());
buf.put_u8(1); // Message type
buf.put_slice(user.as_bytes());
buf.put_u16(*sequence);
buf.put_slice(data);
buf.freeze()
}
_ => panic!("Client messages cannot be serialized"),
}
}
pub fn to_vec(&self) -> Vec<u8> {
// pas très optimisé
self.to_bytes().to_vec()
}
pub fn message_type(&self) -> UDPMessageType {
match self {
Self::ClientPing { .. } | Self::ServerPing { .. } => UDPMessageType::Ping,
Self::ClientAudio { .. } | Self::ServerAudio { .. } => UDPMessageType::Audio,
}
}
// Calcule la taille du message sérialisé
pub fn size(&self) -> usize {
match self {
Self::ClientPing { .. } | Self::ServerPing { .. } => 17, // 1 + 16 (UUID)
Self::ClientAudio { data, .. } => 3 + data.len(), // 1 + 2 + audio_data
Self::ServerAudio { data, .. } => 19 + data.len(), // 1 + 16 + 2 + audio_data
}
}
}
impl UDPMessage {
// Parsing depuis slice -> Bytes (zero-copy si possible)
pub fn from_client_bytes(address: SocketAddr, data: &[u8]) -> Result<Self, ParseError> {
let original_size = data.len();
let bytes = Bytes::copy_from_slice(data); // Seule allocation
let data = UDPMessageData::from_client_bytes(bytes)?;
Ok(Self {
data,
address,
size: original_size
})
}
// Constructeurs server
pub fn server_ping(address: SocketAddr, message_id: Uuid) -> Self {
let data = UDPMessageData::server_ping(message_id);
let size = data.size();
Self { data, address, size }
}
pub fn server_audio(address: SocketAddr, user: Uuid, sequence: u16, data: Bytes) -> Self {
let msg_data = UDPMessageData::server_audio(user, sequence, data);
let size = msg_data.size();
Self { data: msg_data, address, size }
}
// Helpers
pub fn to_bytes(&self) -> Bytes {
self.data.to_bytes()
}
pub fn to_vec(&self) -> Vec<u8> {
self.data.to_vec()
}
pub fn message_type(&self) -> UDPMessageType {
self.data.message_type()
}
// Getters
pub fn size(&self) -> usize {
self.size
}
pub fn address(&self) -> SocketAddr {
self.address
}
}
impl UDPMessage {
// Helpers pour récupérer certain éléments des messages
pub fn get_message_id(&self) -> Option<Uuid> {
match &self.data {
UDPMessageData::ClientPing { message_id } => Some(*message_id),
UDPMessageData::ServerPing { message_id } => Some(*message_id),
_ => None,
}
}
}
// Helper pour compatibilité avec UDPMessageType
impl UDPMessageType {
pub fn from_message(data: &[u8]) -> Option<Self> {
if data.is_empty() {
return None;
}
Self::from_repr(data[0])
}
}
impl UdpBroadcastMessage {
// Constructeurs server
pub fn server_ping(addresses: HashSet<SocketAddr>, message_id: Uuid) -> Self {
let data = UDPMessageData::server_ping(message_id);
let size = data.size();
Self { data, addresses, size }
}
pub fn server_audio(addresses: HashSet<SocketAddr>, user: Uuid, sequence: u16, data: Bytes) -> Self {
let msg_data = UDPMessageData::server_audio(user, sequence, data);
let size = msg_data.size();
Self { data: msg_data, addresses, size }
}
// Conversion vers messages individuels (pour compatibilité)
pub fn to_individual_messages(&self) -> Vec<UDPMessage> {
self.addresses.iter().map(|&addr| {
UDPMessage {
data: self.data.clone(),
address: addr,
size: self.size,
}
}).collect()
}
// Helpers
pub fn to_bytes(&self) -> Bytes {
self.data.to_bytes()
}
pub fn addresses(&self) -> &HashSet<SocketAddr> {
&self.addresses
}
pub fn size(&self) -> usize {
self.size
}
}

104
src/network/udp.rs Normal file
View File

@@ -0,0 +1,104 @@
use tokio::net::UdpSocket;
use std::error::Error;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::task::AbortHandle;
use crate::domain::event::{Event, EventBus};
use crate::network::protocol::{UDPMessage, UdpBroadcastMessage};
#[derive(Clone)]
pub struct UdpServer {
event_bus: EventBus,
socket: Arc<UdpSocket>,
abort_handle: Option<AbortHandle>,
}
impl UdpServer {
pub async fn new(event_bus: EventBus, addr: &str) -> Self {
let socket = UdpSocket::bind(addr).await.unwrap();
let addr = socket.local_addr().unwrap();
println!("Socket UDP lié avec succès on {}", addr);
Self {
event_bus,
socket: Arc::new(socket),
abort_handle: None
}
}
pub async fn start(&mut self) -> Result<(), Box<dyn Error>> {
println!("Démarrage du serveur UDP...");
let event_bus = self.event_bus.clone();
let socket = self.socket.clone();
let recv_task = tokio::spawn(async move {
// Buffer réutilisable pour éviter les allocations
let mut buf = vec![0u8; 1500];
loop {
match socket.recv_from(&mut buf).await {
Ok((size, address)) => {
// Slice du buffer pour éviter de copier des données inutiles
if let Ok(message) = UDPMessage::from_client_bytes(address, &buf[..size]) {
event_bus.emit(Event::UdpIn(message)).await;
}
// Sinon, on ignore silencieusement le message malformé
}
Err(e) => {
match e.kind() {
std::io::ErrorKind::ConnectionReset |
std::io::ErrorKind::ConnectionAborted => {
// Silencieux pour les déconnexions normales
continue;
}
_ => {
println!("Erreur UDP: {}", e);
continue;
}
}
}
}
}
});
self.abort_handle = Some(recv_task.abort_handle());
Ok(())
}
pub async fn send_udp_message(&self, message: &UDPMessage) -> bool {
match self.socket.send_to(&message.to_bytes(), message.address()).await {
Ok(size) => {
self.event_bus.emit(Event::UdpOut(message.clone())).await;
true
}
Err(e) => {
println!("Erreur lors de l'envoi du message à {}: {}", message.address(), e);
false
}
}
}
pub async fn broadcast_udp_message(&self, message: &UdpBroadcastMessage) -> bool {
let bytes = message.to_bytes();
for &address in message.addresses() {
match self.socket.send_to(&bytes, address).await {
Ok(_) => {
// Emit individual event pour tracking
let individual_msg = UDPMessage {
data: message.data.clone(),
address,
size: message.size,
};
self.event_bus.emit(Event::UdpOut(individual_msg)).await;
}
Err(e) => {
println!("Erreur broadcast vers {}: {}", address, e);
}
}
}
true
}
}

193
src/network/udp_back.rs Normal file
View File

@@ -0,0 +1,193 @@
use tokio::net::UdpSocket;
use tokio::sync::RwLock;
use std::error::Error;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use dashmap::DashMap;
use tokio::task::AbortHandle;
use tokio::time::{sleep, Instant};
use crate::domain::client::Client;
use crate::domain::event::{Event, EventBus};
use crate::network::protocol::{UdpClientMessage, UdpServerMessage};
#[derive(Clone)]
pub struct UdpServer {
event_bus: EventBus,
socket: Arc<UdpSocket>,
abort_handle: Option<AbortHandle>,
clients: Arc<DashMap<SocketAddr, Client>>,
}
impl UdpServer {
pub async fn new(event_bus: EventBus, addr: &str) -> Self {
let socket = UdpSocket::bind(addr).await.unwrap();
let addr = socket.local_addr().unwrap();
println!("Socket UDP lié avec succès on {}", addr);
Self {
event_bus,
socket: Arc::new(socket),
abort_handle: None,
clients: Arc::new(DashMap::new()),
}
}
pub async fn start(&mut self) -> Result<(), Box<dyn Error>> {
println!("Démarrage du serveur UDP...");
let event_bus = self.event_bus.clone();
let socket = self.socket.clone();
let clients = self.clients.clone();
let recv_task = tokio::spawn(async move {
let mut buf = [0u8; 1500];
loop {
match socket.recv_from(&mut buf).await {
Ok((size, address)) => {
// Ajouter le client à la liste
// todo : solution vraiment pas idéal, il faudrait vraiment la repenser avec un système helo/bye
if !clients.contains_key(&address) {
let client = Client::new(address);
clients.insert(address, client);
println!("Nouveau client connecté: {}", address);
}else {
let mut client = clients.get_mut(&address).unwrap();
client.update_last_seen();
}
if let Ok(message) = UdpClientMessage::from_bytes(&buf[..size]) {
let event = Event::UdpIn { address, size, message };
event_bus.emit(event).await;
} else {
println!("Erreur lors du parsing du message de {}: {:?}", address, &buf[..size]);
}
}
Err(e) => {
match e.kind() {
std::io::ErrorKind::ConnectionReset |
std::io::ErrorKind::ConnectionAborted => {
// Silencieux pour les déconnexions normales
continue;
}
_ => {
println!("Erreur UDP: {}", e);
continue;
}
}
}
}
}
});
self.abort_handle = Some(recv_task.abort_handle());
Ok(())
}
pub async fn send(&self, address: SocketAddr, message: UdpServerMessage) -> bool {
let event_bus = self.event_bus.clone();
match self.socket.send_to(&message.to_byte(), address).await {
Ok(size) => {
event_bus.emit(Event::UdpOut { address, size, message }).await;
true
}
Err(e) => {
println!("Erreur lors de l'envoi du message à {}: {}", address, e);
// Optionnel : retirer le client si l'adresse est invalide
self.remove_client(address).await;
false
}
}
}
pub async fn group_send(&self, addr_list: Vec<SocketAddr>, message: UdpServerMessage) -> bool {
if addr_list.is_empty() {
return true;
}
let socket = self.socket.clone();
let clients = self.clients.clone();
let send_tasks: Vec<_> = addr_list.into_iter().map(|address| {
let event_bus = self.event_bus.clone();
let message_clone = message.clone();
let socket_clone = socket.clone();
let clients_clone = clients.clone();
tokio::spawn(async move {
match socket_clone.send_to(&message_clone.to_byte(), address).await {
Ok(size) => {
event_bus.emit(Event::UdpOut { address, size, message: message_clone }).await;
true
}
Err(e) => {
println!("Erreur lors de l'envoi du message à {}: {}", address, e);
// Optionnel : retirer le client si l'adresse est invalide
if clients_clone.contains_key(&address) {
clients_clone.remove(&address);
println!("Client {} retiré de la liste", address);
}
false
}
}
})
}).collect();
let mut all_success = true;
for task in send_tasks {
match task.await {
Ok(success) => {
if !success {
all_success = false;
}
}
Err(_) => {
all_success = false;
}
}
}
all_success
}
pub async fn all_send(&self, message: UdpServerMessage) -> bool {
let client_addresses = self.get_clients().await;
self.group_send(client_addresses, message).await
}
pub async fn get_clients(&self) -> Vec<SocketAddr> {
self.clients.iter()
.map(|entry| *entry.key())
.collect()
}
// Nouvelle méthode pour nettoyer les clients déconnectés
async fn remove_client(&self, address: SocketAddr) {
if self.clients.contains_key(&address){
self.clients.remove(&address);
println!("Client {} retiré de la liste", address);
}else {
println!("Client {} n'est pas dans la liste", address);
}
}
// Méthode pour nettoyer les clients inactifs
pub async fn cleanup_inactive_clients(&self) {
let timeout = Duration::from_secs(10);
let now = Instant::now();
let mut to_remove = Vec::new();
for entry in self.clients.iter() {
let address = *entry.key();
let client = entry.value();
if now.duration_since(client.last_seen()) > timeout {
to_remove.push(address);
}
}
for address in &to_remove {
println!("Suppression du client {}", address);
self.clients.remove(address);
}
}
}

87
src/runtime/dispatcher.rs Normal file
View File

@@ -0,0 +1,87 @@
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::task::AbortHandle;
use crate::domain::client::ClientManager;
use crate::domain::event::{Event, EventBus};
use crate::network::protocol::{UDPMessageType, UDPMessage};
use crate::network::udp::UdpServer;
#[derive(Clone)]
pub struct Dispatcher {
event_bus: EventBus,
udp_server: UdpServer,
client_manager: ClientManager
}
impl Dispatcher {
pub async fn new(event_bus: EventBus, udp_server: UdpServer, client_manager: ClientManager) -> Self {
Self {
event_bus,
udp_server,
client_manager,
}
}
pub async fn start(&self, mut receiver: mpsc::Receiver<Event>) {
let (udp_in_abort_handle, udp_in_sender) = self.udp_in_handler().await;
while let Some(event) = receiver.recv().await {
match event {
Event::UdpIn(message) => {
let _ = udp_in_sender.send(message).await;
// // println!("Message reçu de {}: {:?}", address, message);
// let udp_server = self.udp_server.clone();
// tokio::spawn(async move {
// match message {
// UdpClientMessage::Ping {message_id} => {
// let send = UdpServerMessage::ping(message_id);
// let _ = udp_server.all_send(send);
// }
// UdpClientMessage::Audio {sequence, data} => {
// let tmp_user_id = Uuid::new_v4();
// let send = UdpServerMessage::audio(tmp_user_id, sequence, data);
// let _ = udp_server.all_send(send).await;
// }
// }
// });
}
Event::UdpOut(message) => {
// println!("Message envoyé à {}: {:?}", address, message);
}
Event::TickSeconds => {
self.client_manager.cleanup(Duration::from_secs(10));
}
_ => {
println!("Event non prit en charge : {:?}", event)
}
}
}
}
pub async fn udp_in_handler(&self) -> (AbortHandle, mpsc::Sender<UDPMessage>) {
let (sender, mut consumer) = mpsc::channel::<UDPMessage>(1024);
let udp_server = self.udp_server.clone();
let task = tokio::spawn(async move {
while let Some(message) = consumer.recv().await {
// Traitement direct du message sans double parsing
match message.message_type() {
UDPMessageType::Ping => {
let response_message = UDPMessage::server_ping(message.address, message.get_message_id().unwrap());
let _ = udp_server.send_udp_message(&response_message);
}
UDPMessageType::Audio => {
// Traiter l'audio
}
}
}
});
(task.abort_handle(), sender)
}
}

1
src/runtime/mod.rs Normal file
View File

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

398
src/utils/byte_utils.rs Normal file
View File

@@ -0,0 +1,398 @@
use uuid::Uuid;
/// Helpers pour la manipulation de bytes - idéal pour les protocoles binaires
///
/// Cette structure permet de lire séquentiellement des données binaires
/// en maintenant une position de lecture interne.
pub struct ByteReader<'a> {
/// Référence vers les données à lire
data: &'a [u8],
/// Position actuelle dans le buffer de lecture
position: usize,
}
impl<'a> ByteReader<'a> {
/// Crée un nouveau lecteur de bytes à partir d'un slice
///
/// # Arguments
/// * `data` - Le slice de bytes à lire
///
/// # Example
/// ```text
/// let data = &[0x01, 0x02, 0x03, 0x04];
/// let reader = ByteReader::new(data);
/// ```
pub fn new(data: &'a [u8]) -> Self {
Self { data, position: 0 }
}
/// Retourne le nombre de bytes restants à lire
///
/// Utilise `saturating_sub` pour éviter les débordements
/// si la position dépasse la taille des données
pub fn remaining(&self) -> usize {
self.data.len().saturating_sub(self.position)
}
/// Vérifie si tous les bytes ont été lus
///
/// # Returns
/// `true` si il n'y a plus de bytes à lire
pub fn is_empty(&self) -> bool {
self.remaining() == 0
}
/// Retourne la position actuelle de lecture
pub fn position(&self) -> usize {
self.position
}
/// Déplace la position de lecture à l'index spécifié
///
/// La position est automatiquement limitée à la taille des données
/// pour éviter les débordements
///
/// # Arguments
/// * `position` - Nouvelle position de lecture
pub fn seek(&mut self, position: usize) {
self.position = position.min(self.data.len());
}
/// Lit un byte (u8) à la position actuelle
///
/// # Returns
/// * `Ok(u8)` - La valeur lue si disponible
/// * `Err(&'static str)` - Si la fin du buffer est atteinte
pub fn read_u8(&mut self) -> Result<u8, &'static str> {
if self.position < self.data.len() {
let value = self.data[self.position];
self.position += 1;
Ok(value)
} else {
Err("Not enough data for u8")
}
}
/// Lit un entier 16-bit en big-endian
///
/// # Returns
/// * `Ok(u16)` - La valeur lue si 2 bytes sont disponibles
/// * `Err(&'static str)` - Si moins de 2 bytes sont disponibles
pub fn read_u16_be(&mut self) -> Result<u16, &'static str> {
if self.remaining() >= 2 {
let value = u16::from_be_bytes([
self.data[self.position],
self.data[self.position + 1],
]);
self.position += 2;
Ok(value)
} else {
Err("Not enough data for u16")
}
}
/// Lit un entier 32-bit en big-endian
///
/// # Returns
/// * `Ok(u32)` - La valeur lue si 4 bytes sont disponibles
/// * `Err(&'static str)` - Si moins de 4 bytes sont disponibles
pub fn read_u32_be(&mut self) -> Result<u32, &'static str> {
if self.remaining() >= 4 {
let value = u32::from_be_bytes([
self.data[self.position],
self.data[self.position + 1],
self.data[self.position + 2],
self.data[self.position + 3],
]);
self.position += 4;
Ok(value)
} else {
Err("Not enough data for u32")
}
}
/// Lit un entier 64-bit en big-endian
///
/// # Returns
/// * `Ok(u64)` - La valeur lue si 8 bytes sont disponibles
/// * `Err(&'static str)` - Si moins de 8 bytes sont disponibles
pub fn read_u64_be(&mut self) -> Result<u64, &'static str> {
if self.remaining() >= 8 {
let value = u64::from_be_bytes([
self.data[self.position],
self.data[self.position + 1],
self.data[self.position + 2],
self.data[self.position + 3],
self.data[self.position + 4],
self.data[self.position + 5],
self.data[self.position + 6],
self.data[self.position + 7],
]);
self.position += 8;
Ok(value)
} else {
Err("Not enough data for u64")
}
}
/// Lit un UUID (16 bytes) à la position actuelle
///
/// Les UUIDs sont stockés sous forme de 16 bytes consécutifs.
/// Cette méthode lit ces 16 bytes et les convertit en UUID.
///
/// # Returns
/// * `Ok(Uuid)` - L'UUID lu si 16 bytes sont disponibles
/// * `Err(&'static str)` - Si moins de 16 bytes sont disponibles
pub fn read_uuid(&mut self) -> Result<Uuid, &'static str> {
if self.remaining() >= 16 {
let uuid_bytes = self.read_fixed_bytes::<16>()?;
Ok(Uuid::from_bytes(uuid_bytes))
} else {
Err("Not enough data for UUID")
}
}
/// Lit une séquence de bytes de longueur spécifiée
///
/// # Arguments
/// * `len` - Nombre de bytes à lire
///
/// # Returns
/// * `Ok(&[u8])` - Slice des bytes lus si disponibles
/// * `Err(&'static str)` - Si pas assez de bytes disponibles
pub fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], &'static str> {
if self.remaining() >= len {
let slice = &self.data[self.position..self.position + len];
self.position += len;
Ok(slice)
} else {
Err("Not enough data for bytes")
}
}
/// Lit un tableau de bytes de taille fixe définie à la compilation
///
/// Utilise les generics const pour définir la taille du tableau
///
/// # Returns
/// * `Ok([u8; N])` - Tableau de bytes lu si disponible
/// * `Err(&'static str)` - Si pas assez de bytes disponibles
pub fn read_fixed_bytes<const N: usize>(&mut self) -> Result<[u8; N], &'static str> {
if self.remaining() >= N {
let mut array = [0u8; N];
array.copy_from_slice(&self.data[self.position..self.position + N]);
self.position += N;
Ok(array)
} else {
Err("Not enough data for fixed bytes")
}
}
/// Lit tous les bytes restants dans le buffer
///
/// Après cet appel, le reader sera vide (position = taille totale)
///
/// # Returns
/// Slice contenant tous les bytes restants
pub fn read_remaining(&mut self) -> &'a [u8] {
let slice = &self.data[self.position..];
self.position = self.data.len();
slice
}
}
/// Structure pour construire séquentiellement des données binaires
///
/// Contrairement à ByteReader, cette structure possède ses propres données
/// et permet d'écrire des valeurs de différents types.
pub struct ByteWriter {
/// Buffer interne pour stocker les données écrites
data: Vec<u8>,
}
impl ByteWriter {
/// Crée un nouveau writer avec un Vec vide
pub fn new() -> Self {
Self { data: Vec::new() }
}
/// Crée un nouveau writer avec une capacité pré-allouée
///
/// Utile pour éviter les réallocations si la taille finale
/// est approximativement connue
///
/// # Arguments
/// * `capacity` - Capacité initiale du buffer
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: Vec::with_capacity(capacity),
}
}
/// Écrit un byte (u8) dans le buffer
///
/// # Arguments
/// * `value` - Valeur à écrire
pub fn write_u8(&mut self, value: u8) {
self.data.push(value);
}
/// Écrit un entier 16-bit en big-endian
///
/// # Arguments
/// * `value` - Valeur à écrire
pub fn write_u16_be(&mut self, value: u16) {
self.data.extend_from_slice(&value.to_be_bytes());
}
/// Écrit un entier 32-bit en big-endian
///
/// # Arguments
/// * `value` - Valeur à écrire
pub fn write_u32_be(&mut self, value: u32) {
self.data.extend_from_slice(&value.to_be_bytes());
}
/// Écrit un entier 64-bit en big-endian
///
/// # Arguments
/// * `value` - Valeur à écrire
pub fn write_u64_be(&mut self, value: u64) {
self.data.extend_from_slice(&value.to_be_bytes());
}
/// Écrit une séquence de bytes dans le buffer
///
/// # Arguments
/// * `bytes` - Slice de bytes à écrire
pub fn write_bytes(&mut self, bytes: &[u8]) {
self.data.extend_from_slice(bytes);
}
/// Écrit un tableau de bytes de taille fixe
///
/// # Arguments
/// * `bytes` - Tableau de bytes à écrire
pub fn write_fixed_bytes<const N: usize>(&mut self, bytes: [u8; N]) {
self.data.extend_from_slice(&bytes);
}
/// Consomme le writer et retourne le Vec contenant les données
///
/// # Returns
/// Vec<u8> contenant toutes les données écrites
pub fn into_vec(self) -> Vec<u8> {
self.data
}
/// Retourne une référence vers les données sous forme de slice
///
/// # Returns
/// Slice des données écrites
pub fn as_slice(&self) -> &[u8] {
&self.data
}
/// Retourne la taille actuelle du buffer
pub fn len(&self) -> usize {
self.data.len()
}
/// Vérifie si le buffer est vide
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
/// Implémentation du trait Default pour ByteWriter
///
/// Permet d'utiliser ByteWriter::default() comme équivalent de ByteWriter::new()
impl Default for ByteWriter {
fn default() -> Self {
Self::new()
}
}
/// Fonctions utilitaires standalone pour la lecture directe sans état
///
/// Ces fonctions permettent de lire des valeurs à des offsets spécifiques
/// sans avoir besoin de créer un ByteReader.
/// Lit un byte à l'offset spécifié
///
/// # Arguments
/// * `data` - Slice de données source
/// * `offset` - Position de lecture
///
/// # Returns
/// * `Some(u8)` - Valeur lue si l'offset est valide
/// * `None` - Si l'offset dépasse la taille des données
pub fn read_u8_at(data: &[u8], offset: usize) -> Option<u8> {
data.get(offset).copied()
}
/// Lit un entier 16-bit big-endian à l'offset spécifié
///
/// # Arguments
/// * `data` - Slice de données source
/// * `offset` - Position de lecture
///
/// # Returns
/// * `Some(u16)` - Valeur lue si 2 bytes sont disponibles à l'offset
/// * `None` - Si pas assez de bytes disponibles
pub fn read_u16_be_at(data: &[u8], offset: usize) -> Option<u16> {
if data.len() >= offset + 2 {
Some(u16::from_be_bytes([data[offset], data[offset + 1]]))
} else {
None
}
}
/// Lit un entier 32-bit big-endian à l'offset spécifié
///
/// # Arguments
/// * `data` - Slice de données source
/// * `offset` - Position de lecture
///
/// # Returns
/// * `Some(u32)` - Valeur lue si 4 bytes sont disponibles à l'offset
/// * `None` - Si pas assez de bytes disponibles
pub fn read_u32_be_at(data: &[u8], offset: usize) -> Option<u32> {
if data.len() >= offset + 4 {
Some(u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]))
} else {
None
}
}
/// Lit un entier 64-bit big-endian à l'offset spécifié
///
/// # Arguments
/// * `data` - Slice de données source
/// * `offset` - Position de lecture
///
/// # Returns
/// * `Some(u64)` - Valeur lue si 8 bytes sont disponibles à l'offset
/// * `None` - Si pas assez de bytes disponibles
pub fn read_u64_be_at(data: &[u8], offset: usize) -> Option<u64> {
if data.len() >= offset + 8 {
Some(u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]))
} else {
None
}
}

1
src/utils/mod.rs Normal file
View File

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