From f8684a0402a8cf7e900bb60fecffa76b63f836dd Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 21:13:47 +0300 Subject: [PATCH] listeners and handlers --- Cargo.lock | 16 +++++ Cargo.toml | 1 + src/config.rs | 4 +- src/context.rs | 118 ++++++++++++++++++++++++++++++++++ src/data.rs | 8 +-- src/main.rs | 171 ++++++++++++++++++++++++++++++++++--------------- 6 files changed, 261 insertions(+), 57 deletions(-) create mode 100644 src/context.rs diff --git a/Cargo.lock b/Cargo.lock index 24ef358..f7dc68a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,12 @@ dependencies = [ "serde", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -221,6 +227,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -323,6 +338,7 @@ dependencies = [ name = "rust_minecraft_server" version = "0.1.0" dependencies = [ + "itertools", "rust_mc_proto", "serde", "serde_default", diff --git a/Cargo.toml b/Cargo.toml index 23348d1..cf60f6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ serde_json = "1.0.140" serde_with = { version = "3.12.0", features = ["macros"] } serde_default = "0.2.0" toml = "0.8.22" +itertools = "0.14.0" diff --git a/src/config.rs b/src/config.rs index 404fd73..c8ededa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,8 +13,8 @@ pub struct ServerConfig { #[serde(default = "default_timeout")] pub timeout: u64, } -fn default_host() -> String { return "127.0.0.1:25565".to_string(); } -fn default_timeout() -> u64 { return 5; } +fn default_host() -> String { "127.0.0.1:25565".to_string() } +fn default_timeout() -> u64 { 5 } impl ServerConfig { pub fn load_from_file(path: PathBuf) -> Option { diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..4269a53 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,118 @@ +use std::{net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicI32, AtomicU16, Ordering}, Arc, RwLock, RwLockWriteGuard}}; + +use itertools::Itertools; +use rust_mc_proto::{MinecraftConnection, Packet}; + +use crate::{config::ServerConfig, data::ServerError}; + +pub struct ServerContext { + pub config: Arc, + listeners: Vec>, + handlers: Vec> +} + +impl ServerContext { + pub fn new(config: Arc) -> ServerContext { + ServerContext { + config, + listeners: Vec::new(), + handlers: Vec::new() + } + } + + pub fn add_packet_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } + + pub fn add_listener(&mut self, listener: Box) { + self.listeners.push(listener); + } + + pub fn packet_handlers( + self: &Arc, + sort_by: F + ) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K + { + self.handlers.iter().sorted_by_key(sort_by).collect_vec() + } + + pub fn listeners( + self: &Arc, + sort_by: F + ) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K + { + self.listeners.iter().sorted_by_key(sort_by).collect_vec() + } +} + +pub struct ClientContext { + pub server: Arc, + pub conn: RwLock>, + pub addr: SocketAddr, + protocol_version: AtomicI32, + server_address: RwLock, + server_port: AtomicU16, +} + +impl ClientContext { + pub fn new( + server: Arc, + conn: MinecraftConnection + ) -> ClientContext { + ClientContext { + server, + addr: conn.get_ref().peer_addr().unwrap(), + conn: RwLock::new(conn), + protocol_version: AtomicI32::default(), + server_address: RwLock::new(String::new()), + server_port: AtomicU16::default() + } + } + + pub fn handshake( + self: &Arc, + protocol_version: i32, + server_address: String, + server_port: u16 + ) -> () { + self.protocol_version.store(protocol_version, Ordering::SeqCst); + self.server_port.store(server_port, Ordering::SeqCst); + *self.server_address.write().unwrap() = server_address; + } + + pub fn protocol_version(self: &Arc) -> i32 { + self.protocol_version.load(Ordering::SeqCst) + } + + pub fn server_port(self: &Arc) -> u16 { + self.server_port.load(Ordering::SeqCst) + } + + pub fn server_address(self: &Arc) -> String { + self.server_address.read().unwrap().clone() + } + + pub fn conn(self: &Arc) -> RwLockWriteGuard<'_, MinecraftConnection> { + self.conn.write().unwrap() + } +} + +pub trait Listener: Sync + Send { + fn on_status_priority(&self) -> i8 { 0 } + fn on_status(&self, _: Arc, _: &mut String) -> Result<(), ServerError> { Ok(()) } +} + +pub trait PacketHandler: Sync + Send { + fn on_incoming_packet_priority(&self) -> i8 { 0 } + fn on_incoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } + + fn on_outcoming_packet_priority(&self) -> i8 { 0 } + fn on_outcoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } +} + diff --git a/src/data.rs b/src/data.rs index 8c0e0a0..19d148b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -137,13 +137,13 @@ impl TextComponentBuilder { } } - pub fn text(mut self, text: String) -> Self { - self.text = text; + pub fn text(mut self, text: &str) -> Self { + self.text = text.to_string(); self } - pub fn color(mut self, color: String) -> Self { - self.color = Some(color); + pub fn color(mut self, color: &str) -> Self { + self.color = Some(color.to_string()); self } diff --git a/src/main.rs b/src/main.rs index 75c9869..269c4c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use std::{env::args, io::{Read, Write}, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; use config::ServerConfig; +use context::{ClientContext, Listener, PacketHandler, ServerContext}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; use data::{ServerError, TextComponent}; @@ -8,8 +9,72 @@ use pohuy::Pohuy; pub mod config; pub mod data; +pub mod context; pub mod pohuy; + +struct ExampleListener; + +impl Listener for ExampleListener { + fn on_status(&self, client: Arc, response: &mut String) -> Result<(), ServerError> { + *response = format!( + "{{ + \"version\": {{ + \"name\": \"idk\", + \"protocol\": {} + }}, + \"players\": {{ + \"max\": 100, + \"online\": 42, + \"sample\": [ + {{ + \"name\": \"Жопа\", + \"id\": \"00000000-0000-0000-0000-000000000000\" + }} + ] + }}, + \"description\": {}, + \"favicon\": \"data:image/png;base64,\", + \"enforcesSecureChat\": false + }}", + client.protocol_version(), + TextComponent::builder() + .text("Hello World! ") + .extra(vec![ + TextComponent::builder() + .text("Protocol: ") + .color("gold") + .extra(vec![ + TextComponent::builder() + .text(&client.protocol_version().to_string()) + .underlined(true) + .build() + ]) + .build(), + TextComponent::builder() + .text("\nServer Addr: ") + .color("green") + .extra(vec![ + TextComponent::builder() + .text(&format!("{}:{}", client.server_address(), client.server_port())) + .underlined(true) + .build() + ]) + .build() + ]) + .build() + .to_string()? + ); + + Ok(()) + } +} + +struct ExamplePacketHandler; + +impl PacketHandler for ExamplePacketHandler {} + + fn main() { // Получение аргументов let exec = args().next().expect("Неизвестная система"); @@ -37,28 +102,45 @@ fn main() { // но мы этого делать не будем чтобы не было мемори лик лишнего let config = Arc::new(config); + // Создаем контекст сервера + // Передается во все подключения + let mut server = ServerContext::new(config); + + server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера + server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + + // Бетонируем сервер контекст от изменений + let server = Arc::new(server); + // Биндим сервер где надо - let Ok(server) = TcpListener::bind(&config.host) else { - println!("Не удалось забиндить сервер на {}", &config.host); + let Ok(listener) = TcpListener::bind(&server.config.host) else { + println!("Не удалось забиндить сервер на {}", &server.config.host); return; }; - println!("Сервер запущен на {}", &config.host); + println!("Сервер запущен на {}", &server.config.host); - while let Ok((stream, addr)) = server.accept() { - let config = config.clone(); + while let Ok((stream, addr)) = listener.accept() { + let server = server.clone(); thread::spawn(move || { println!("Подключение: {}", addr); // Установка таймаутов на чтение и запись // По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг - stream.set_read_timeout(Some(Duration::from_secs(config.timeout))).pohuy(); - stream.set_write_timeout(Some(Duration::from_secs(config.timeout))).pohuy(); + stream.set_read_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy(); + stream.set_write_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy(); + + // Оборачиваем стрим в майнкрафт конекшн лично для нашего удовольствия + let conn = MinecraftConnection::new(stream); + + // Создаем контекст клиента + // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент + let client = Arc::new(ClientContext::new(server, conn)); // Обработка подключения // Если ошибка -> выводим - match handle_connection(config, MinecraftConnection::new(&stream)) { + match handle_connection(client) { Ok(_) => {}, Err(error) => { println!("Ошибка подключения: {error:?}"); @@ -71,11 +153,13 @@ fn main() { } fn handle_connection( - _: Arc, // Конфиг сервера (возможно будет использоаться в будущем) - mut conn: MinecraftConnection // Подключение + client: Arc, // Контекст клиента ) -> Result<(), ServerError> { // Чтение рукопожатия - let mut packet = conn.read_packet()?; + // Получение пакетов производится через client.conn(), + // ВАЖНО: не помещать сам client.conn() в переменные, + // он должен сразу убиваться иначе соединение гдето задедлочится + let mut packet = client.conn().read_packet()?; if packet.id() != 0x00 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); @@ -86,56 +170,41 @@ fn handle_connection( let server_port = packet.read_unsigned_short()?; // Все тоже самое что и с адресом сервера и все потому же и за тем же let next_state = packet.read_varint()?; // Тип подключения: 1 для получения статуса и пинга, 2 и 3 для обычного подключения + client.handshake(protocol_version, server_address, server_port); + match next_state { 1 => { // Тип подключения - статус loop { // Чтение запроса - let packet = conn.read_packet()?; + let packet = client.conn().read_packet()?; match packet.id() { 0x00 => { // Запрос статуса let mut packet = Packet::empty(0x00); + // Дефолтный статус + let mut status = "{ + \"version\": { + \"name\": \"Error\", + \"protocol\": 0 + }, + \"description\": {\"text\": \"Internal server error\"} + }".to_string(); + + // Опрос всех листенеров + for listener in client.server.listeners( // Цикл по листенерам + |o| o.on_status_priority() // Сортировка по приоритетности + ).iter() { + listener.on_status(client.clone(), &mut status)?; // Вызов метода листенера + } + // Отправка статуса - // В будущем это надо будет переделать чтобы это отправлялось через Listener'ы а не самим ядром сервера - // Хотя можно сделать и дефолтное значение через конфиг - packet.write_string(&format!( - // Пример статуса - "{{ - \"version\": {{ - \"name\": \"1.21.5\", - \"protocol\": {protocol_version} - }}, - \"players\": {{ - \"max\": 100, - \"online\": 5, - \"sample\": [ - {{ - \"name\": \"thinkofdeath\", - \"id\": \"4566e69f-c907-48ee-8d71-d7ba5aa00d20\" - }} - ] - }}, - \"description\": {}, - \"favicon\": \"data:image/png;base64,\", - \"enforcesSecureChat\": false - }}", + packet.write_string(&status)?; - // В MOTD пихаем дебаг инфу - TextComponent::builder() - .text(format!("pv: {protocol_version}, sp: {server_port}\nsa: {server_address}")) - .color("red".to_string()) - .bold(true) - .italic(true) - .underlined(true) - .build() - .to_string()? - ))?; - - conn.write_packet(&packet)?; + client.conn().write_packet(&packet)?; }, 0x01 => { // Пинг - conn.write_packet(&packet)?; + client.conn().write_packet(&packet)?; // Просто отправляем этот же пакет обратно // ID такой-же, содержание тоже, так почему бы и нет? }, @@ -151,13 +220,13 @@ fn handle_connection( let mut packet = Packet::empty(0x00); packet.write_string(&TextComponent::builder() - .text(format!("This server is in developement!!")) - .color("gold".to_string()) + .text("This server is in developement!!") + .color("gold") .bold(true) .build() .to_string()?)?; - conn.write_packet(&packet)?; + client.conn().write_packet(&packet)?; // TODO: Чтение Configuration (возможно с примешиванием Listener'ов) // TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы