init
This commit is contained in:
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