init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
8
.idea/modules.xml
generated
Normal 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
11
.idea/ox_speak_server.iml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
639
Cargo.lock
generated
Normal 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
25
Cargo.toml
Normal 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
BIN
profile.json.gz
Normal file
Binary file not shown.
66
src/app/app.rs
Normal file
66
src/app/app.rs
Normal 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
1
src/app/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod app;
|
||||||
0
src/core/mod.rs
Normal file
0
src/core/mod.rs
Normal file
162
src/domain/client.rs
Normal file
162
src/domain/client.rs
Normal 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
40
src/domain/event.rs
Normal 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
3
src/domain/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod event;
|
||||||
|
pub mod user;
|
||||||
|
pub mod client;
|
||||||
47
src/domain/user.rs
Normal file
47
src/domain/user.rs
Normal 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
6
src/lib.rs
Normal 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
19
src/main.rs
Normal 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
2
src/network/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod protocol;
|
||||||
|
pub mod udp;
|
||||||
246
src/network/protocol.rs
Normal file
246
src/network/protocol.rs
Normal 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
104
src/network/udp.rs
Normal 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
193
src/network/udp_back.rs
Normal 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
87
src/runtime/dispatcher.rs
Normal 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
1
src/runtime/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod dispatcher;
|
||||||
398
src/utils/byte_utils.rs
Normal file
398
src/utils/byte_utils.rs
Normal 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
1
src/utils/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod byte_utils;
|
||||||
Reference in New Issue
Block a user