initial commit

This commit is contained in:
Luuk van Oijen 2023-11-07 22:48:41 +01:00
commit 38aaf29fe8
12 changed files with 2995 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

82
BEAMMP_PROTOCOL_DOCS.md Normal file
View File

@ -0,0 +1,82 @@
# BeamMP Protocol Documentation
WIP of course, but hope it helps others
## TCP
### Basic message format
All messages are sent as strings (?).
Every packet starts with a 4 byte header, denoting how many bytes the packet data is.
This means that you first must read 4 bytes from your socket, to get the header data, and afterwards read the data ready for reading.
### Communication flow
Upon client connection:
1. Authentication
2. Sync with client?
#### Authentication
1. C->S - Client sends the server a single byte. The BeamMP source checks for 3 letters, but I only saw C
2. C->S - Client sends a packet containing the client version
3. S->C - Server sends the server a regular packet containing only 1 byte: `S` (the ascii character)
4. C->S - Client sends the server its public key
5. S->C - Server sends the client their assigned server ID
#### Syncing resources
1. C->S - Client sends the server a packet with the code `SR`
2. S->C - Server sends the client a packet containing a filelist containing the name and size of files to download, or "-" if it's empty
### Packets
All packets start with a single character, denoted as "Packet Code" or simply "Code" down below.
The data mentioned is merely example data, to show the data format. This includes the code!
Example:
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `C` | C->S | Client version | VC2.0 |
#### Uncategorized
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `J` | S->C | Show a message in the top left on the clients screen | JWelcome Luuk! |
#### Authentication
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `C` | C->S | Client version | VC2.0 |
| `S` | S->C | Confirmation? | None |
| `D` | C->S | Start download phase? Seems unused from testing | None |
| `P` | S->C | Send client their assigned ID | P123 |
#### Syncing resources
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `SR` | C->S | Client requests a list containing all mods, containing their name and file size | None |
| `f` | C->S | Client requests file with name | fcool_mod.zip |
| `-` | S->C | Technically not a packet, see [Syncing Resources](https://github.com/Lucky4Luuk/beammp_rust_server/blob/master/BEAMMP_PROTOCOL_DOCS.md#syncing-resources), step 2. | * |
#### Syncing client state
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `H` | C->S | Client requests a full sync with the server state | None |
### Vehicle packets
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `Os` | C->S | Client informs server they are spawning a car | Os:C_ID:{car_json} |
| `Os` | S->C | Server sends this packet to all clients, informing them a client has spawned a car | Os:C_ROLES:C_NAME:C_ID-CAR_ID:CAR_JSON |
| `Or` | C->S | Client informs server they are respawning their car | Or:C_ID-CAR_ID:{respawn_json} |
| `Or` | S->C | Server informs all other clients that a client is respawning their car | Or:C_ID-CAR_ID:{respawn_json} |
| `Od` | C->S | Client informs server they are deleting their car | Od:C_ID-CAR_ID |
| `Od` | S->C | Server informs all other clients that a client is deleting their car | Od:C_ID-CAR_ID |
| `Oc` | C->S | Client informs server they are editing their car | Oc:?... |
| `Oc` | S->C | Server informs all other clients that a client has edited their car | Oc:?... |
## UDP
### Packets
#### Uncategorized
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `p` | C->S | Ping! | None |
| `p` | S->C | Pong! | None |
#### Vehicle related
| Code | Dir | Explanation | Data |
| ---- | --- | ----------- | ---- |
| `Zp` | C->S | Positional packet | Zp:C_ID-CAR_ID:{transform_json} |

1482
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

24
Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "beammp_rust_server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
pretty_env_logger = "0.4.0"
anyhow = "1.0.66"
nalgebra = "0.31"
num_enum = "0.5.7"
async-trait = "0.1.58"
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "net", "io-util", "sync"] }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "*"
toml = "0.5"
flate2 = "1.0"

37
ServerConfig.toml Normal file
View File

@ -0,0 +1,37 @@
# This is the BeamMP-Server config file.
# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`
# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under "Keys"
[General]
Name = "bepis big gaming"
Port = 30814
# AuthKey has to be filled out in order to run the server
AuthKey = "test"
# Whether to log chat messages in the console / log
LogChat = true
Debug = false
Private = true
MaxCars = 20
MaxPlayers = 800000
Map = "/levels/west_coast_usa/info.json"
Description = "BeamMP Default Description"
ResourceFolder = "Resources"
[Misc]
# Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.
ImScaredOfUpdates = false
# If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`
SendErrorsShowMessage = true
# You can turn on/off the SendErrors message you get on startup here
SendErrors = true
[HTTP]
# Which IP to listen on. Pick 0.0.0.0 for a public-facing server with no specific IP, and 127.0.0.1 or 'localhost' for a local server.
HTTPServerIP = "127.0.0.1"
SSLKeyPath = "./.ssl/HttpServer/key.pem"
SSLCertPath = "./.ssl/HttpServer/cert.pem"
HTTPServerPort = 8080
# Recommended to have enabled for servers which face the internet. With SSL the server will serve https and requires valid key and cert files
UseSSL = false
# Enables the internal HTTP server
HTTPServerEnabled = false

38
src/config.rs Normal file
View File

@ -0,0 +1,38 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Config {
#[serde(rename = "General")]
pub general: GeneralSettings,
}
#[derive(Deserialize)]
pub struct GeneralSettings {
#[serde(rename = "Port")]
pub port: Option<u16>,
#[serde(rename = "MaxCars")]
pub max_cars: Option<u8>,
#[serde(rename = "MaxPlayers")]
pub max_players: usize,
#[serde(rename = "Map")]
pub map: String,
// Options below are not yet supported
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "LogChat")]
pub log_chat: bool,
#[serde(rename = "Debug")]
pub debug: bool,
#[serde(rename = "AuthKey")]
pub auth_key: Option<String>,
#[serde(rename = "Private")]
pub private: bool,
#[serde(rename = "Description")]
pub description: String,
#[serde(rename = "ResourceFolder")]
pub resource_folder: String,
}

28
src/main.rs Normal file
View File

@ -0,0 +1,28 @@
#[macro_use] extern crate async_trait;
#[macro_use] extern crate log;
mod server;
mod config;
#[tokio::main]
async fn main() {
let user_config: config::Config = toml::from_str(
&std::fs::read_to_string("ServerConfig.toml")
.map_err(|_| error!("Failed to read config file!"))
.expect("Failed to read config file!")
)
.map_err(|_| error!("Failed to parse config file!"))
.expect("Failed to parse config file!");
let user_config = std::sync::Arc::new(user_config);
let mut server = server::Server::new(user_config)
.await
.map_err(|e| error!("{:?}", e))
.expect("Failed to start server!");
loop {
if let Err(e) = server.process().await {
error!("{:?}", e);
}
}
}

18
src/server/backend.rs Normal file
View File

@ -0,0 +1,18 @@
use serde::de::DeserializeOwned;
use std::collections::HashMap;
static BACKEND_URL: &'static str = "backend.beammp.com";
static AUTH_URL: &'static str = "auth.beammp.com";
pub async fn authentication_request<R: DeserializeOwned>(
target: &str,
map: HashMap<String, String>,
) -> anyhow::Result<R> {
let client = reqwest::Client::new();
let resp = client
.post(format!("https://{}/{}", AUTH_URL, target))
.json(&map)
.send()
.await?;
Ok(resp.json().await?)
}

35
src/server/car.rs Normal file
View File

@ -0,0 +1,35 @@
use nalgebra::*;
use std::time::{Instant, Duration};
#[derive(Default, Clone, Debug)]
pub struct Car {
pub car_json: String,
pub pos: Vector3<f64>,
pub rot: Quaternion<f64>,
pub vel: Vector3<f64>,
pub rvel: Vector3<f64>,
pub tim: f64,
pub ping: f64,
pub last_pos_update: Option<Instant>,
}
impl Car {
pub fn new(car_json: String) -> Self {
Self {
car_json: car_json,
..Default::default()
}
}
pub fn pos(&self) -> Vector3<f64> {
self.pos + self.vel * self.last_pos_update.map(|t| t.elapsed().as_secs_f64()).unwrap_or(0.0)
}
pub fn rotation(&self) -> Quaternion<f64> {
let t = self.last_pos_update.map(|t| t.elapsed().as_secs_f64()).unwrap_or(0.0);
self.rot + UnitQuaternion::from_euler_angles(self.rvel.x * t, self.rvel.y * t, self.rvel.z * t).quaternion()
}
}

488
src/server/client.rs Normal file
View File

@ -0,0 +1,488 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use std::ops::DerefMut;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Instant;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpStream,
};
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use nalgebra::*;
use serde::Deserialize;
use super::backend::*;
use super::car::*;
use super::packet::*;
static ATOMIC_ID_COUNTER: AtomicU8 = AtomicU8::new(0);
#[derive(PartialEq)]
pub enum ClientState {
None,
Connecting,
SyncingResources,
Syncing,
Disconnect,
}
#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct UserData {
pub createdAt: String,
pub guest: bool,
pub roles: String,
pub username: String,
}
pub struct Client {
pub id: u8,
pub udp_addr: Option<SocketAddr>,
socket: OwnedReadHalf,
write_half: Arc<Mutex<OwnedWriteHalf>>,
write_runtime: JoinHandle<()>,
write_runtime_sender: Sender<Packet>,
pub state: ClientState,
pub info: Option<UserData>,
pub cars: Vec<(u8, Car)>,
pub ready: bool,
pub grid_spot: usize,
pub finished: bool,
pub incidents: usize,
pub last_physics_update: Instant,
}
impl Drop for Client {
fn drop(&mut self) {
self.write_runtime.abort();
}
}
impl Client {
pub fn new(socket: TcpStream) -> Self {
let id = ATOMIC_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
trace!("Client with ID #{} created!", id);
let (read_half, write_half) = socket.into_split();
let (tx, mut rx): (Sender<Packet>, Receiver<Packet>) = tokio::sync::mpsc::channel(128);
let write_half = Arc::new(Mutex::new(write_half));
let write_half_ref = Arc::clone(&write_half);
let handle: JoinHandle<()> = tokio::spawn(async move {
loop {
if let Some(packet) = rx.recv().await {
// trace!("Runtime received packet...");
let mut lock = write_half_ref.lock().await;
// trace!("Runtime sending packet!");
if let Err(e) = tcp_write(lock.deref_mut(), packet).await {
error!("{:?}", e);
};
// trace!("Runtime sent packet!");
drop(lock);
}
}
});
Self {
id: id,
udp_addr: None,
socket: read_half,
write_half: write_half,
write_runtime: handle,
write_runtime_sender: tx,
state: ClientState::Connecting,
info: None,
cars: Vec::new(),
ready: false,
grid_spot: 0,
finished: false,
incidents: 0,
last_physics_update: Instant::now(),
}
}
pub async fn authenticate(&mut self, config: &super::Config) -> anyhow::Result<()> {
debug!("Authenticating client {}...", self.id);
'waiting_for_c: loop {
self.socket.readable().await?;
let mut tmp = vec![0u8; 1];
while self.socket.peek(&mut tmp).await? == 0 {}
// Authentication works a little differently than normal
// Not sure why, but the BeamMP source code shows they
// also only read a single byte during authentication
let code = self.read_raw(1).await?[0];
debug!("code: '{}' / {}", code as char, code);
match code as char {
'C' => {
// TODO: Check client version
trace!("Client version packet");
self.socket.readable().await?;
let packet = self.read_packet_waiting().await?;
debug!("{:?}", packet);
break 'waiting_for_c;
}
'D' => {
let id = self.read_raw(1).await?[0];
debug!("HandleDownload connection for client id: {}", id);
debug!("Not handling this for now!");
return Err(ClientError::IsDownloader.into());
}
_ => {
return Err(ClientError::AuthenticateError.into());
}
}
}
self.write_packet(Packet::Raw(RawPacket::from_code('S')))
.await?;
self.socket.readable().await?;
if let Some(packet) = self.read_packet_waiting().await? {
debug!("packet: {:?}", packet);
if packet.data.len() > 50 {
self.kick("Player key too big!").await;
return Err(ClientError::AuthenticateError.into());
}
let mut json = HashMap::new();
json.insert("key".to_string(), packet.data_as_string());
let user_data: UserData =
authentication_request("pkToUser", json)
.await
.map_err(|e| {
error!("{:?}", e);
e
})?;
debug!("user_data: {:?}", user_data);
self.info = Some(user_data);
} else {
self.kick(
"Client never sent public key! If this error persists, try restarting your game.",
)
.await;
}
self.write_packet(Packet::Raw(RawPacket::from_str(&format!("P{}", self.id))))
.await?;
self.state = ClientState::SyncingResources;
debug!(
"Authentication of client {} succesfully completed! Syncing now...",
self.id
);
self.sync(config).await?;
Ok(())
}
// TODO: https://github.com/BeamMP/BeamMP-Server/blob/master/src/TNetwork.cpp#L619
pub async fn sync(&mut self, config: &super::Config) -> anyhow::Result<()> {
'syncing: while self.state == ClientState::SyncingResources {
self.socket.readable().await?;
if let Some(packet) = self.read_packet().await? {
if packet.data.len() == 0 {
continue;
}
if packet.data.len() == 4 {
if packet.data == [68, 111, 110, 101] {
break 'syncing;
}
}
match packet.data[0] as char {
'S' if packet.data.len() > 1 => match packet.data[1] as char {
'R' => {
let file_packet = RawPacket::from_code('-');
// let file_data = "/bepis_dysoon_uu201_v3.zip;/bepis_laudi_v8_revolution.zip;/simraceclubclient.zip;48353220;50283849;1937;";
// let file_packet = RawPacket::from_str(file_data);
self.write_packet(Packet::Raw(file_packet))
.await?
}
_ => error!("Unknown packet! {:?}", packet),
}
'f' => {
// Handle file download
let mut file_name = packet.data_as_string().clone();
file_name.remove(0); // Remove f
debug!("Client requested file {}", file_name);
self.kick(&format!("You have not yet downloaded {}!", file_name)).await;
// let mut lock = self.write_half.lock().await;
// lock.writable().await?;
// trace!("Sending packet!");
// if let Err(e) = tcp_send_file(lock.deref_mut(), file_name).await {
// error!("{:?}", e);
// }
}
_ => error!("Unknown packet! {:?}", packet),
}
}
}
self.state = ClientState::None;
trace!("Done syncing! Sending map name...");
self.write_packet(Packet::Raw(RawPacket::from_str(&format!(
"M{}",
config.general.map
))))
.await?;
trace!("Map name sent!");
Ok(())
}
/// This function should never block. It should simply check if there's a
/// packet, and then and only then should it read it. If this were to block, the server
/// would come to a halt until this function unblocks.
pub async fn process(&mut self) -> anyhow::Result<Option<RawPacket>> {
if let Some(packet) = self.read_packet().await? {
return Ok(Some(packet));
}
Ok(None)
}
pub fn disconnect(&mut self) {
self.state = ClientState::Disconnect;
}
pub async fn kick(&mut self, msg: &str) {
// let _ = self.socket.writable().await;
// let _ = self.write_packet(Packet::Raw(RawPacket::from_str(&format!("K{}", msg)))).await;
// self.disconnect();
let _ = self
.write_packet(Packet::Raw(RawPacket::from_str(&format!("K{}", msg))))
.await;
self.disconnect();
}
pub fn get_name(&self) -> &str {
&self.info.as_ref().unwrap().username
}
pub fn get_id(&self) -> u8 {
self.id
}
pub fn get_roles(&self) -> &str {
&self.info.as_ref().unwrap().roles
}
pub fn register_car(&mut self, car: Car) -> u8 {
let mut free_num = 0;
for (num, _) in &self.cars {
if num == &free_num {
free_num += 1;
} else {
break;
}
}
self.cars.push((free_num, car));
free_num
}
pub fn unregister_car(&mut self, car_id: u8) {
let prev_len = self.cars.len();
self.cars.retain(|(id, _)| id != &car_id);
if prev_len == self.cars.len() {
error!(
"Failed to unregister car #{} for client #{}! Ignoring for now...",
car_id, self.id
);
}
}
pub fn get_car_mut(&mut self, mut car_id: u8) -> Option<&mut Car> {
for (num, car) in &mut self.cars {
if num == &mut car_id {
return Some(car);
}
}
None
}
async fn read_raw(&mut self, count: usize) -> anyhow::Result<Vec<u8>> {
let mut b = vec![0u8; count];
self.socket.read_exact(&mut b).await?;
Ok(b)
}
async fn read_packet_waiting(&mut self) -> anyhow::Result<Option<RawPacket>> {
let start = std::time::Instant::now();
'wait: loop {
if let Some(packet) = self.read_packet().await? {
return Ok(Some(packet));
}
if start.elapsed().as_secs() >= 5 {
break 'wait;
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
Err(ClientError::ConnectionTimeout.into())
}
/// Must be non-blocking
async fn read_packet(&mut self) -> anyhow::Result<Option<RawPacket>> {
let mut header = [0u8; 4];
match self.socket.try_read(&mut header) {
Ok(0) => {
error!("Socket is readable, yet has 0 bytes to read! Disconnecting client...");
self.disconnect();
return Ok(None);
}
Ok(_n) => {}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
return Ok(None);
}
Err(e) => return Err(e.into()),
}
let expected_size = u32::from_le_bytes(header) as usize;
let mut data = Vec::new();
let mut tmp_data = vec![0u8; expected_size];
let mut data_size = 0;
while data_size < expected_size {
match self.socket.try_read(&mut tmp_data) {
Ok(0) => {
error!("Socket is readable, yet has 0 bytes to read! Disconnecting client...");
self.disconnect();
return Ok(None);
}
Ok(n) => {
data_size += n;
data.extend_from_slice(&tmp_data[..n]);
},
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// debug!("Packet appears to be ready, yet can't be read yet!");
// self.socket.read(&mut data).await?;
return Ok(None);
}
Err(e) => return Err(e.into()),
}
}
Ok(Some(RawPacket {
header: data_size as u32,
data: data[..data_size].to_vec(),
}))
}
/// Blocking write
pub async fn write_packet(&mut self, packet: Packet) -> anyhow::Result<()> {
let mut lock = self.write_half.lock().await;
lock.writable().await?;
trace!("Sending packet!");
if let Err(e) = tcp_write(lock.deref_mut(), packet).await {
error!("{:?}", e);
}
trace!("Packet sent!");
drop(lock);
Ok(())
}
pub async fn queue_packet(&self, packet: Packet) {
// TODO: If packet gets lost, put it back at the front of the queue?
let _ = self.write_runtime_sender.send(packet).await;
}
pub async fn trigger_client_event<S: Into<String>, D: Into<String>>(&self, event_name: S, data: D) {
let event_name = event_name.into();
let data = data.into();
debug!("Calling client event '{}' with data '{}'", event_name, data);
let packet_data = format!("E:{}:{}", event_name, data);
self.queue_packet(Packet::Raw(RawPacket::from_str(&packet_data))).await;
}
}
#[derive(Debug)]
pub enum ClientError {
AuthenticateError,
ConnectionTimeout,
IsDownloader,
}
impl std::fmt::Display for ClientError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{:?}", self)?;
Ok(())
}
}
impl std::error::Error for ClientError {}
#[async_trait]
trait Writable {
async fn writable(&self) -> std::io::Result<()>;
}
#[async_trait]
impl Writable for TcpStream {
async fn writable(&self) -> std::io::Result<()> {
self.writable().await
}
}
#[async_trait]
impl Writable for OwnedWriteHalf {
async fn writable(&self) -> std::io::Result<()> {
self.writable().await
}
}
async fn tcp_write<W: AsyncWriteExt + Writable + std::marker::Unpin>(
w: &mut W,
mut packet: Packet,
) -> anyhow::Result<()> {
let compressed = match packet.get_code() {
Some('O') => true,
Some('T') => true,
_ => packet.get_data().len() > 400,
};
if compressed {
let mut compressed: Vec<u8> = Vec::with_capacity(100_000);
let mut compressor = flate2::Compress::new(flate2::Compression::best(), true);
compressor.compress_vec(
packet.get_data(),
&mut compressed,
flate2::FlushCompress::Sync,
)?;
let mut new_data = "ABG:".as_bytes()[..4].to_vec();
new_data.append(&mut compressed);
packet.set_header(new_data.len() as u32);
packet.set_data(new_data);
}
tcp_write_raw(w, packet).await?;
Ok(())
}
async fn tcp_write_raw<W: AsyncWriteExt + Writable + std::marker::Unpin>(
w: &mut W,
mut packet: Packet,
) -> anyhow::Result<()> {
let mut raw_data: Vec<u8> = packet.get_header().to_le_bytes().to_vec();
raw_data.extend_from_slice(packet.get_data());
w.writable().await?;
w.write(&raw_data).await?;
Ok(())
}
async fn tcp_send_file<W: AsyncWriteExt + Writable + std::marker::Unpin>(
w: &mut W,
file_name: String,
) -> anyhow::Result<()> {
debug!("Sending file '{}'", file_name);
tcp_write(w, Packet::Raw(RawPacket::from_str("KYou have not downloaded the mod manually!"))).await?;
Ok(())
}

631
src/server/mod.rs Normal file
View File

@ -0,0 +1,631 @@
use std::net::SocketAddr;
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}};
use std::time::Instant;
use tokio::net::{TcpListener, UdpSocket};
use tokio::task::JoinHandle;
use num_enum::IntoPrimitive;
use nalgebra::*;
mod backend;
mod car;
mod client;
mod packet;
pub use backend::*;
pub use car::*;
pub use client::*;
pub use packet::*;
pub use crate::config::Config;
#[derive(PartialEq, IntoPrimitive, Copy, Clone, Debug)]
#[repr(u8)]
enum ServerState {
Unknown = 0,
WaitingForClients,
WaitingForReady,
WaitingForSpawns,
Qualifying,
LiningUp,
Countdown,
Race,
Finish,
}
pub struct Server {
tcp_listener: Arc<TcpListener>,
udp_socket: Arc<UdpSocket>,
clients_incoming: Arc<Mutex<Vec<Client>>>,
pub clients: Vec<Client>,
connect_runtime_handle: JoinHandle<()>,
config: Arc<Config>,
}
impl Server {
pub async fn new(config: Arc<Config>) -> anyhow::Result<Self> {
let config_ref = Arc::clone(&config);
let port = config.general.port.unwrap_or(48900);
let tcp_listener = {
let bind_addr = &format!("0.0.0.0:{}", port);
Arc::new(TcpListener::bind(bind_addr).await?)
};
let tcp_listener_ref = Arc::clone(&tcp_listener);
let udp_socket = {
let bind_addr = &format!("0.0.0.0:{}", port);
Arc::new(UdpSocket::bind(bind_addr).await?)
};
let clients_incoming = Arc::new(Mutex::new(Vec::new()));
let clients_incoming_ref = Arc::clone(&clients_incoming);
debug!("Client acception runtime starting...");
let connect_runtime_handle = tokio::spawn(async move {
loop {
match tcp_listener_ref.accept().await {
Ok((socket, addr)) => {
info!("New client connected: {:?}", addr);
let mut client = Client::new(socket);
match client.authenticate(&config_ref).await {
Ok(_) => {
let mut lock = clients_incoming_ref
.lock()
.map_err(|e| error!("{:?}", e))
.expect("Failed to acquire lock on mutex!");
lock.push(client);
drop(lock);
},
Err(e) => {
error!("Authentication error occured, kicking player...");
error!("{:?}", e);
client.kick("Failed to authenticate player!").await;
}
}
}
Err(e) => error!("Failed to accept incoming connection: {:?}", e),
}
}
});
debug!("Client acception runtime started!");
Ok(Self {
tcp_listener: tcp_listener,
udp_socket: udp_socket,
clients_incoming: clients_incoming,
clients: Vec::new(),
connect_runtime_handle: connect_runtime_handle,
config: config,
})
}
pub async fn process(&mut self) -> anyhow::Result<()> {
// Bit weird, but this is all to avoid deadlocking the server if anything goes wrong
// with the client acception runtime. If that one locks, the server won't accept
// more clients, but it will at least still process all other clients
let mut joined_names = Vec::new();
if let Ok(mut clients_incoming_lock) = self.clients_incoming.try_lock() {
if clients_incoming_lock.len() > 0 {
trace!(
"Accepting {} incoming clients...",
clients_incoming_lock.len()
);
for i in 0..clients_incoming_lock.len() {
joined_names.push(
clients_incoming_lock[i]
.info
.as_ref()
.unwrap()
.username
.clone(),
);
self.clients.push(clients_incoming_lock.swap_remove(i));
}
trace!("Accepted incoming clients!");
}
}
// Process UDP packets
// TODO: Use a UDP addr -> client ID look up table
for (addr, packet) in self.read_udp_packets().await {
if packet.data.len() == 0 {
continue;
}
let id = packet.data[0] - 1; // Offset by 1
let data = packet.data[2..].to_vec();
let packet_processed = RawPacket {
header: data.len() as u32,
data,
};
'search: for i in 0..self.clients.len() {
if self.clients[i].id == id {
self.parse_packet_udp(i, addr, packet_processed).await?;
break 'search;
}
}
}
// Process all the clients (TCP)
let mut packets: Vec<(usize, RawPacket)> = Vec::new();
for i in 0..self.clients.len() {
if let Some(client) = self.clients.get_mut(i) {
match client.process().await {
Ok(packet_opt) => {
if let Some(raw_packet) = packet_opt {
packets.push((i, raw_packet.clone()));
}
}
Err(e) => client.kick(&format!("Kicked: {:?}", e)).await,
}
// More efficient than broadcasting as we are already looping
for name in joined_names.iter() {
self.clients[i]
.queue_packet(Packet::Notification(NotificationPacket::new(format!(
"Welcome {}!",
name.to_string()
))))
.await;
}
}
}
for (i, packet) in packets {
self.parse_packet(i, packet).await?
}
// I'm sorry for this code :(
for i in 0..self.clients.len() {
if self.clients.get(i).ok_or(ServerError::ClientDoesntExist)?.state == ClientState::Disconnect {
let id = self.clients.get(i).ok_or(ServerError::ClientDoesntExist)?.id;
for j in 0..self.clients.get(i).ok_or(ServerError::ClientDoesntExist)?.cars.len() {
let car_id = self.clients.get(i).ok_or(ServerError::ClientDoesntExist)?.cars[j].0;
let delete_packet = format!("Od:{}-{}", id, car_id);
self.broadcast(Packet::Raw(RawPacket::from_str(&delete_packet)), None)
.await;
}
info!("Disconnecting client {}...", id);
self.clients.remove(i);
info!("Client {} disconnected!", id);
}
}
Ok(())
}
async fn broadcast(&self, packet: Packet, owner: Option<u8>) {
for client in &self.clients {
if let Some(id) = owner {
if id == client.id {
continue;
}
}
client.queue_packet(packet.clone()).await;
}
}
async fn broadcast_udp(&self, packet: Packet, owner: Option<u8>) {
for client in &self.clients {
if let Some(id) = owner {
if id == client.id {
continue;
}
}
// client.queue_packet(packet.clone()).await;
if let Some(udp_addr) = client.udp_addr {
self.send_udp(udp_addr, &packet).await;
}
}
}
async fn send_udp(&self, udp_addr: SocketAddr, packet: &Packet) {
let data = packet.get_data();
if data.len() > 400 {
trace!("Compressing...");
let mut compressed: Vec<u8> = Vec::with_capacity(100_000);
let mut compressor = flate2::Compress::new(flate2::Compression::best(), true);
if let Err(e) = compressor.compress_vec(
data,
&mut compressed,
flate2::FlushCompress::Sync,
) {
error!("Compression failed!");
return;
}
let mut new_data = "ABG:".as_bytes()[..4].to_vec();
new_data.append(&mut compressed);
if let Err(e) = self.udp_socket.try_send_to(&new_data, udp_addr) {
error!("UDP Packet send error: {:?}", e);
}
} else {
if let Err(e) = self.udp_socket.try_send_to(&data, udp_addr) {
error!("UDP Packet send error: {:?}", e);
}
}
}
async fn read_udp_packets(&self) -> Vec<(SocketAddr, RawPacket)> {
let mut packets = Vec::new();
'read: loop {
let mut data = vec![0u8; 4096];
let data_size;
let data_addr;
match self.udp_socket.try_recv_from(&mut data) {
Ok((0, _)) => {
error!("UDP socket is readable, yet has 0 bytes to read!");
break 'read;
}
Ok((n, addr)) => (data_size, data_addr) = (n, addr),
Err(_) => break 'read,
}
let packet = RawPacket {
header: data_size as u32,
data: data[..data_size].to_vec(),
};
packets.push((data_addr, packet));
}
packets
}
async fn parse_packet_udp(
&mut self,
client_idx: usize,
udp_addr: SocketAddr,
mut packet: RawPacket,
) -> anyhow::Result<()> {
if packet.data.len() > 0 {
let client = &mut self.clients[client_idx];
let client_id = client.get_id();
client.udp_addr = Some(udp_addr);
// Check if compressed
let mut is_compressed = false;
if packet.data.len() > 3 {
let string_data = String::from_utf8_lossy(&packet.data[..4]);
if string_data.starts_with("ABG:") {
is_compressed = true;
trace!("Packet is compressed!");
}
}
if is_compressed {
let compressed = &packet.data[4..];
let mut decompressed: Vec<u8> = Vec::with_capacity(100_000);
let mut decompressor = flate2::Decompress::new(true);
decompressor.decompress_vec(
compressed,
&mut decompressed,
flate2::FlushDecompress::Finish,
)?;
packet.header = decompressed.len() as u32;
packet.data = decompressed;
// let string_data = String::from_utf8_lossy(&packet.data[..]);
// debug!("Unknown packet - String data: `{}`; Array: `{:?}`; Header: `{:?}`", string_data, packet.data, packet.header);
}
// Check packet identifier
let packet_identifier = packet.data[0] as char;
if packet.data[0] >= 86 && packet.data[0] <= 89 {
self.broadcast_udp(Packet::Raw(packet), Some(client_id))
.await;
} else {
match packet_identifier {
'p' => {
self.send_udp(udp_addr, &Packet::Raw(RawPacket::from_code('p')))
.await;
}
'Z' => {
if packet.data.len() < 7 {
error!("Position packet too small!");
return Err(ServerError::BrokenPacket.into());
} else {
// Sent as text so removing 48 brings it from [48-57] to [0-9]
let client_id = packet.data[3] - 48;
let car_id = packet.data[5] - 48;
let pos_json = &packet.data[7..];
let pos_data: TransformPacket =
serde_json::from_str(&String::from_utf8_lossy(pos_json))?;
let p = Packet::Raw(packet);
for i in 0..self.clients.len() {
if self.clients[i].id == client_id {
let client = &mut self.clients[i];
let car = client
.get_car_mut(car_id)
.ok_or(ServerError::CarDoesntExist)?;
car.pos = pos_data.pos.into();
car.rot = Quaternion::new(
pos_data.rot[3],
pos_data.rot[0],
pos_data.rot[1],
pos_data.rot[2],
);
car.vel = pos_data.vel.into();
car.rvel = pos_data.rvel.into();
car.tim = pos_data.tim;
car.ping = pos_data.ping;
car.last_pos_update = Some(Instant::now());
} else {
if let Some(udp_addr) = self.clients[i].udp_addr {
self.send_udp(udp_addr, &p).await;
}
}
}
}
}
_ => {
let string_data = String::from_utf8_lossy(&packet.data[..]);
debug!(
"Unknown packet UDP - String data: `{}`; Array: `{:?}`; Header: `{:?}`",
string_data, packet.data, packet.header
);
}
}
}
}
Ok(())
}
async fn parse_packet(
&mut self,
client_idx: usize,
mut packet: RawPacket,
) -> anyhow::Result<()> {
if packet.data.len() > 0 {
let client_id = {
let client = &mut self.clients[client_idx];
client.get_id()
};
// Check if compressed
let mut is_compressed = false;
if packet.data.len() > 3 {
let string_data = String::from_utf8_lossy(&packet.data[..4]);
if string_data.starts_with("ABG:") {
is_compressed = true;
// trace!("Packet is compressed!");
}
}
if is_compressed {
let compressed = &packet.data[4..];
let mut decompressed: Vec<u8> = Vec::with_capacity(100_000);
let mut decompressor = flate2::Decompress::new(true);
decompressor.decompress_vec(
compressed,
&mut decompressed,
flate2::FlushDecompress::Finish,
)?;
packet.header = decompressed.len() as u32;
packet.data = decompressed;
// let string_data = String::from_utf8_lossy(&packet.data[..]);
// debug!("Unknown packet - String data: `{}`; Array: `{:?}`; Header: `{:?}`", string_data, packet.data, packet.header);
}
// Check packet identifier
if packet.data[0] >= 86 && packet.data[0] <= 89 {
self.broadcast(Packet::Raw(packet), Some(client_id)).await;
} else {
let packet_identifier = packet.data[0] as char;
match packet_identifier {
'H' => {
// Full sync with server
self.clients[client_idx]
.queue_packet(Packet::Raw(RawPacket::from_str(&format!(
"Sn{}",
self.clients[client_idx]
.info
.as_ref()
.unwrap()
.username
.clone()
))))
.await;
// TODO: Sync all existing cars on server (this code is broken)
for client in &self.clients {
let pid = client.id as usize;
if pid != client_idx {
for (vid, car) in &client.cars {
self.clients[client_idx]
.queue_packet(Packet::Raw(RawPacket::from_str(&format!(
"Os:{vid}:{}",
car.car_json,
))))
.await;
}
}
}
}
'O' => self.parse_vehicle_packet(client_idx, packet).await?,
'C' => {
// TODO: Chat filtering?
let packet_data = packet.data_as_string();
let message = packet_data.split(":").collect::<Vec<&str>>().get(2).map(|s| s.to_string()).unwrap_or(String::new());
let message = message.trim();
if message.starts_with("!") {
if message == "!ready" {
self.clients[client_idx].ready = true;
self.clients[client_idx].queue_packet(Packet::Raw(RawPacket::from_str("C:Server:You are now ready!"))).await;
} else if message == "!pos" {
let car = &self.clients[client_idx].cars.get(0).ok_or(ServerError::CarDoesntExist)?.1;
trace!("car transform (pos/rot/vel/rvel): {:?}", (car.pos, car.rot, car.vel, car.rvel));
} else {
self.clients[client_idx].queue_packet(Packet::Raw(RawPacket::from_str("C:Server:Unknown command!"))).await;
}
} else {
self.broadcast(Packet::Raw(packet), None).await;
}
}
_ => {
let string_data = String::from_utf8_lossy(&packet.data[..]);
debug!(
"Unknown packet - String data: `{}`; Array: `{:?}`; Header: `{:?}`",
string_data, packet.data, packet.header
);
}
}
}
}
Ok(())
}
async fn parse_vehicle_packet(
&mut self,
client_idx: usize,
packet: RawPacket,
) -> anyhow::Result<()> {
if packet.data.len() < 6 {
error!("Vehicle packet too small!");
return Ok(()); // TODO: Return error here
}
let code = packet.data[1] as char;
match code {
's' => {
let client = &mut self.clients[client_idx];
let mut allowed = true;
if let Some(max_cars) = self.config.general.max_cars {
if client.cars.len() >= max_cars as usize { allowed = false; }
}
// trace!("Packet string: `{}`", packet.data_as_string());
let split_data = packet
.data_as_string()
.splitn(3, ':')
.map(|s| s.to_string())
.collect::<Vec<String>>();
let car_json_str = &split_data.get(2).ok_or(std::fmt::Error)?;
// let car_json: serde_json::Value = serde_json::from_str(&car_json_str)?;
let car_id = client.register_car(Car::new(car_json_str.to_string()));
let client_id = client.get_id();
if allowed {
client.trigger_client_event("GetSize", client_id.to_string()).await;
let packet_data = format!(
"Os:{}:{}:{}-{}:{}",
client.get_roles(),
client.get_name(),
client_id,
car_id,
car_json_str
);
let response = RawPacket::from_str(&packet_data);
self.broadcast(Packet::Raw(response), None).await;
info!("Spawned car for client #{}!", client_id);
} else {
let packet_data = format!(
"Os:{}:{}:{}-{}:{}",
client.get_roles(),
client.get_name(),
client_id,
car_id,
car_json_str
);
let response = RawPacket::from_str(&packet_data);
client.write_packet(Packet::Raw(response)).await;
let packet_data = format!(
"Od:{}-{}",
client_id,
car_id,
);
let response = RawPacket::from_str(&packet_data);
client.write_packet(Packet::Raw(response)).await;
client.unregister_car(car_id);
info!("Blocked spawn for client #{}!", client_id);
}
}
'c' => {
// let split_data = packet.data_as_string().splitn(3, ':').map(|s| s.to_string()).collect::<Vec<String>>();
// let car_json_str = &split_data.get(2).ok_or(std::fmt::Error)?;
let client_id = packet.data[3] - 48;
let car_id = packet.data[5] - 48;
let car_json = String::from_utf8_lossy(&packet.data[7..]).to_string();
let response = Packet::Raw(packet.clone());
for i in 0..self.clients.len() {
if self.clients[i].id == client_id {
if let Some(car) = self.clients[i].get_car_mut(car_id) {
car.car_json = car_json.clone();
}
} else {
// Already looping so more efficient to send here
// if let Some(udp_addr) = self.clients[i].udp_addr {
// self.write_udp(udp_addr, &response).await;
// }
self.clients[i].write_packet(response.clone()).await;
}
}
}
'd' => {
debug!("packet: {:?}", packet);
let split_data = packet
.data_as_string()
.splitn(3, [':', '-'])
.map(|s| s.to_string())
.collect::<Vec<String>>();
let client_id = split_data[1].parse::<u8>()?;
let car_id = split_data[2].parse::<u8>()?;
for i in 0..self.clients.len() {
if self.clients[i].id == client_id {
self.clients[i].unregister_car(car_id);
}
// Don't broadcast, we are already looping anyway
// if let Some(udp_addr) = self.clients[i].udp_addr {
// self.send_udp(udp_addr, &Packet::Raw(packet.clone())).await;
// }
self.clients[i].write_packet(Packet::Raw(packet.clone())).await;
}
info!("Deleted car for client #{}!", client_id);
}
'r' => {
self.broadcast(Packet::Raw(packet), Some(self.clients[client_idx].id)).await;
}
't' => {
self.broadcast(Packet::Raw(packet), Some(self.clients[client_idx].id))
.await;
}
'm' => {
self.broadcast(Packet::Raw(packet), None).await;
}
_ => error!("Unknown vehicle related packet!\n{:?}", packet), // TODO: Return error here
}
Ok(())
}
}
impl Drop for Server {
fn drop(&mut self) {
// Not sure how needed this is but it seems right?
self.connect_runtime_handle.abort();
}
}
#[derive(Debug)]
pub enum ServerError {
BrokenPacket,
CarDoesntExist,
ClientDoesntExist,
}
impl std::fmt::Display for ServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{:?}", self)?;
Ok(())
}
}
impl std::error::Error for ServerError {}

131
src/server/packet.rs Normal file
View File

@ -0,0 +1,131 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub enum Packet {
Raw(RawPacket),
Notification(NotificationPacket),
}
impl Packet {
pub fn get_header(&self) -> u32 {
match self {
Self::Raw(raw) => raw.header,
Self::Notification(msg) => self.get_data().len() as u32,
}
}
pub fn get_data(&self) -> &[u8] {
match self {
Self::Raw(raw) => &raw.data,
Self::Notification(p) => p.0.as_bytes(),
}
}
pub fn get_code(&self) -> Option<char> {
match self {
Self::Raw(raw) => raw.data.get(0).map(|c| *c as char),
Self::Notification(_) => Some('J'),
}
}
pub fn set_data(&mut self, data: Vec<u8>) {
match self {
Self::Raw(raw) => raw.data = data,
Self::Notification(_) => todo!(),
}
}
pub fn set_header(&mut self, header: u32) {
match self {
Self::Raw(raw) => raw.header = header,
Self::Notification(_) => todo!(),
}
}
pub fn data_as_string(&self) -> String {
String::from_utf8_lossy(&self.get_data()).to_string()
}
}
#[derive(Debug, Clone)]
pub struct NotificationPacket(String);
impl NotificationPacket {
pub fn new<S: Into<String>>(msg: S) -> Self {
Self(format!("J{}", msg.into()))
}
}
/// Protocol:
/// Header: 4 bytes, contains data size
/// Data: Contains packet data
#[derive(Clone)]
pub struct RawPacket {
pub header: u32,
pub data: Vec<u8>,
}
impl RawPacket {
pub fn from_code(code: char) -> Self {
Self {
header: 1,
data: vec![code as u8],
}
}
pub fn from_str(str_data: &str) -> Self {
let data = str_data.as_bytes().to_vec();
Self {
header: data.len() as u32,
data: data,
}
}
pub fn data_as_string(&self) -> String {
String::from_utf8_lossy(&self.data).to_string()
}
}
impl std::fmt::Debug for RawPacket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"Header: `{:?}` - Bytes: `{:?}` - String: `{}`",
self.header,
self.data,
self.data_as_string()
)?;
Ok(())
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct RespawnPacketData {
pub pos: RespawnPacketDataPos,
pub rot: RespawnPacketDataRot,
}
#[derive(Default, Serialize, Deserialize)]
pub struct RespawnPacketDataPos {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[derive(Default, Serialize, Deserialize)]
pub struct RespawnPacketDataRot {
pub x: f64,
pub y: f64,
pub z: f64,
pub w: f64,
}
#[derive(Default, Serialize, Deserialize)]
pub struct TransformPacket {
pub rvel: [f64; 3],
pub tim: f64,
pub pos: [f64; 3],
pub ping: f64,
pub rot: [f64; 4],
pub vel: [f64; 3],
}