This commit is contained in:
2025-03-06 22:38:53 +01:00
parent 6b406c24d3
commit f7fa5d5be4
8 changed files with 138 additions and 113 deletions
+18 -12
View File
@@ -1,22 +1,28 @@
# SaturnClient API # SaturnClient API
The api for AuraClient which is a Minecraft mod Client The api for AuraClient which is a Minecraft mod Client
# Handshake
In order to communicate with the server, you will have to make a handshake, here is how
```
<MOJANG-SESSION-TOKEN>
```
The session token is stored on your minecraft client when it communicates with Mojang servers, as you might understand, this token is a private token used for authentication with Mojang servers, Why do we need it? Because our server needs to understand that whoever logs in with SaturnClient is you and when you make changes in your client, the changes get synced with others, Example: Changing Cloak.
# Protocol base # Protocol base
All whitespace is used Every request should be only one line
``` ```
method method@key1=value1@key2=value2
@key=value
```
For private requests (requires microsoft session tokens)
```
@SessionToken=<session-token>
``` ```
# Example method # Error handling
request
``` ```
ping invalid-method
``` ```
- Response response
``` ```
pong !Invalid method
``` ```
On a response `!` means it's an error.
-1
View File
@@ -1 +0,0 @@
pub const CLOAKS: &[&str] = &["glitch"];
+18
View File
@@ -0,0 +1,18 @@
pub struct Player {
pub uuid: String,
pub cloaks: Vec<String>,
pub cloak: String,
}
pub struct Database {
pub players: mongodb::sync::Collection<Player>,
}
impl Database {
pub fn new(client: &mongodb::sync::Client) -> Self {
let db = client.database("saturn");
Self {
players: db.collection("players"),
}
}
}
-33
View File
@@ -1,33 +0,0 @@
use mongodb::sync::{Client, Collection};
use serde::{Deserialize, Serialize};
pub enum Response {
Pong,
}
impl Response {
pub fn to_string(&self) -> String {
match self {
Response::Pong => "pong".to_string(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Player {
pub uuid: String,
pub cloaks: Vec<String>,
}
pub struct Database {
pub players: Collection<Player>,
}
impl Database {
pub fn new(client: &Client) -> Self {
let db = client.database("saturn");
Self {
players: db.collection("players"),
}
}
}
+32 -56
View File
@@ -1,68 +1,44 @@
pub mod assets; use std::{
pub mod io; io::{BufRead, BufReader, Write},
net::TcpListener,
sync::Arc,
};
pub mod database;
pub mod methods; pub mod methods;
pub mod parser; pub mod parser;
pub mod response;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use std::thread;
use io::Database;
use methods::handle_request;
use mongodb::sync::Client;
const MONGO_URI: &str = "mongodb://admin:admin@localhost/";
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let client = Client::with_uri_str(MONGO_URI).unwrap(); let client = mongodb::sync::Client::with_uri_str("mongodb://admin:admin@localhost/").unwrap();
let database = Arc::new(Database::new(&client)); let database = Arc::new(database::Database::new(&client));
let listener = TcpListener::bind("127.0.0.1:8080")?;
println!("Server listening on port 8080"); let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() { for stream in listener.incoming() {
match stream { println!("New connection");
Ok(stream) => { let database = Arc::clone(&database);
println!("New connection: {}", stream.peer_addr().unwrap());
thread::spawn({ std::thread::spawn({
let database = Arc::clone(&database); move || {
move || handle_client(stream, database) let mut stream = stream.unwrap();
}); let mut reader = BufReader::new(stream.try_clone().unwrap());
let session = methods::Session::new(&mut reader, database).unwrap();
loop {
let mut request_string = String::new();
match reader.read_line(&mut request_string).unwrap() {
0 => break,
_ => {
let (method, params) = parser::parse(&request_string).unwrap();
let response = session.handle_request(&method, &params);
stream.write_all(response.to_string().as_bytes()).unwrap();
}
};
}
} }
Err(e) => eprintln!("Connection failed: {}", e), });
}
} }
Ok(()) Ok(())
} }
pub fn handle_client(mut stream: TcpStream, database: Arc<Database>) {
let mut buffer = [0; 512];
loop {
match stream.read(&mut buffer) {
Ok(0) => break,
Ok(n) => {
let recv = String::from_utf8_lossy(&buffer[..n]).to_string();
match parser::parse(&recv) {
Ok((method, params)) => {
match handle_request(Arc::clone(&database), method, params) {
Ok(response) => {
stream.write_all(response.to_string().as_bytes()).unwrap()
}
Err(e) => eprintln!("Error handling request: {}", e),
}
}
Err(e) => eprintln!("Error parsing request: {}", e),
};
}
Err(e) => {
eprintln!("Error reading from client: {}", e);
break;
}
}
}
println!("Client disconnected");
}
+34 -10
View File
@@ -1,14 +1,38 @@
use std::{collections::HashMap, sync::Arc}; use std::{
collections::HashMap,
io::{self, BufRead, BufReader},
net::TcpStream,
sync::Arc,
};
use crate::io::{Database, Response}; pub struct Session {
pub session_id: String,
pub database: Arc<crate::database::Database>,
}
pub fn handle_request( impl Session {
database: Arc<Database>, pub fn new(
method: String, reader: &mut BufReader<TcpStream>,
params: HashMap<String, String>, database: Arc<crate::database::Database>,
) -> Result<Response, String> { ) -> io::Result<Self> {
match method.as_str() { let mut session_id = String::new();
"ping" => Ok(Response::Pong), reader.read_line(&mut session_id)?;
_ => Err("Invalid request: ".to_string()),
Ok(Self {
session_id,
database,
})
}
pub fn handle_request(
&self,
method: &str,
params: &HashMap<String, String>,
) -> crate::response::Response {
match method {
"ping" => crate::response::Response::Pong,
_ => crate::response::Response::Error(crate::response::Error::InvalidRequest),
}
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ impl ParamMap for HashMap<String, String> {
} }
} }
pub fn parse(s: &str) -> Result<(String, HashMap<String, String>), String> { pub fn parse(s: &str) -> std::io::Result<(String, HashMap<String, String>)> {
let mut parts = s.split("@"); let mut parts = s.split("@");
let method = parts.next().unwrap().trim().to_lowercase(); let method = parts.next().unwrap().trim().to_lowercase();
+35
View File
@@ -0,0 +1,35 @@
#[derive(Debug, Clone)]
pub enum Response {
Pong,
Error(Error),
}
impl std::fmt::Display for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Response::Pong => write!(f, "Pong"),
Response::Error(error) => write!(f, "!{}", error),
}
}
}
#[derive(Debug, Clone)]
pub enum Error {
InvalidRequest,
InvalidMethod,
InvalidParameter,
ParameterNotFound,
InvalidSessionToken,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::InvalidRequest => write!(f, "Invalid request"),
Error::InvalidMethod => write!(f, "Invalid method"),
Error::InvalidParameter => write!(f, "Invalid parameter"),
Error::ParameterNotFound => write!(f, "Parameter not found"),
Error::InvalidSessionToken => write!(f, "Invalid session token"),
}
}
}