Initial commit (still very WIP)

This commit is contained in:
Funtimes909 committed 2025-02-14 16:16:07 +13:00
commit b7c879b985
6 files changed
+304

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
use serde::Deserialize;
use std::fs::File;
use std::io::{ErrorKind, Read};
#[derive(Deserialize)]
pub struct Config {
pub database: Database,
pub rescanner: Rescanner
}
#[derive(Deserialize)]
pub struct Database {
pub url: String,
pub user: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct Rescanner {
pub rescan_delay: u16,
pub speed: u16,
pub repeat: bool,
pub port_range: String
}
impl Default for Rescanner {
fn default() -> Self {
Self {
rescan_delay: 60,
speed: 1000,
repeat: true,
port_range: "25560 - 25570".to_string()
}
}
}
pub fn load_config(path: String) -> Config {
let mut file = match File::open(&path) {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => panic!("{} not found!", &path),
ErrorKind::PermissionDenied => panic!("File permissions invalid for {}!", &path),
_ => panic!("Unknown config error!"),
}
};
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap_or_default();
toml::from_str(&contents).unwrap()
}
+7
View File
@@ -0,0 +1,7 @@
use crate::response::Server;
use diesel::{Connection, PgConnection};
use diesel::connection::SimpleConnection;
pub fn connect(database_url: &str) -> PgConnection {
PgConnection::establish(&database_url).unwrap_or_else(|err| {panic!("Fatal Error! {}", err)})
}
+30
View File
@@ -0,0 +1,30 @@
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::servers)]
#[diesel(check_for_backend(diesel::pg::Pg))]
#[allow(dead_code)]
pub struct Server {
pub id: i32,
pub address: String,
pub port: bigdecimal::BigDecimal,
pub firstseen: i32,
pub lastseen: i32,
pub country: String,
pub asn: String,
pub reversedns: String,
pub organization: String,
pub version: String,
pub protocol: i32,
pub fmlnetworkversion: i32,
pub motd: String,
pub icon: String,
pub timesseen: i32,
pub preventsreports: bool,
pub enforcesecure: bool,
pub whitelist: bool,
pub cracked: bool,
pub maxplayers: i32,
pub onlineplayers: i32,
pub software: String,
}
+75
View File
@@ -0,0 +1,75 @@
use crate::response::{parse_response, Server};
use std::io::{Error, ErrorKind, Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::str::FromStr;
use std::time::Duration;
const REQUEST: [u8; 9] = [
6, // Size: Amount of bytes in the message
0, // ID: Has to be 0
0, // Protocol Version: Can be anything as long as it's a valid varint
0, // Server address
0, 0, // Port: Can be anything (Notchian servers don't this)
1, // Next state: 1 for status, 2 for login. Therefore, has to be 1
1, // Size
0, // ID
];
pub async fn ping_server(address: &str, port: &u16) -> Result<Server, Error> {
let address = format!("{}:{}", address, port);
let socket = SocketAddr::from_str(address.as_str()).unwrap();
match TcpStream::connect_timeout(&socket, Duration::from_secs(3)) {
Ok(mut stream) => {
stream.write(&REQUEST)?;
let mut buff: [u8; 1024] = [0; 1024];
// Read the first buffer, getting the initial bytes needed
let mut total_read = stream.read(&mut buff)?;
// Grab the final buffer size
let buff_size = decode(&buff.to_vec());
// and use that to calculate the total amount of bytes needed (in the entire packet)
let bytes_needed = buff_size.0 + (buff_size.1 as usize);
let mut out_buff = vec![];
out_buff.extend_from_slice(&buff[..total_read]);
// Just incase, we can also calculate the final size of the json
let json_bytes = decode(&(buff[(buff_size.1+1).into()..]));
// Repeat until we read everything
while total_read < bytes_needed {
let read = stream.read(&mut buff)?;
out_buff.extend_from_slice(&buff[..read]);
total_read += read;
}
let response: String = String::from_utf8_lossy(
&out_buff[
(buff_size.1 + 1 + json_bytes.1).into()
..
]
).to_string();
Ok(parse_response(response.as_str()))
}
Err(_) => {
Err(Error::new(ErrorKind::NetworkUnreachable, "Server did not respond."))
}
}
}
fn decode(bytes: &[u8]) -> (usize, u8) {
let mut val: usize = 0;
let mut count: u8 = 0;
for b in bytes {
val |= ((b & 0x7f) as usize) << count;
if (b >> 7) != 1 {
break;
}
count += 7;
}
(val, (count / 7) + 1)
}
+114
View File
@@ -0,0 +1,114 @@
use std::io::{Error, ErrorKind};
use std::str::FromStr;
use diesel::row::NamedRow;
use serde_json::Value;
pub struct Server {
pub version: Option<String>,
pub protocol: Option<i32>,
pub icon: Option<String>,
pub motd: Option<String>,
pub prevents_reports: Option<bool>,
pub enforces_secure_chat: Option<bool>,
pub mods: Option<Vec<Mod>>,
pub players: Option<Vec<Player>>,
}
pub struct Player {
pub username: String,
// Replace this with a UUID type
pub uuid: String
}
pub struct Mod {
pub mod_id: String,
pub mod_name: String,
}
// None values should insert NULL into the database if no value is found
impl Default for Server {
fn default() -> Self {
Server {
version: None,
protocol: None,
icon: None,
motd: None,
prevents_reports: None,
enforces_secure_chat: None,
mods: None,
players: None,
}
}
}
// Lifetimes will solve this mess
fn parse_motd(motd: Value) -> Result<String, ErrorKind::InvalidData> {
if motd.is_array() {
for value in motd.as_array() {
// Recursion
if value.is_object() || value.is_array() {
parse_motd(value.clone())?;
} else {}
}
} else {
if let Some(object) = motd.as_object() {
for value in object {
// Recursion
if value.is_object() || value.is_array() {
parse_motd(value.clone())?;
}
if let Some(color) = value.get("color") {}
if let Some(bold) = value.get("bold") {}
if let Some(text) = value.get("text") {}
}
}
}
}
// I no longer care about trying to make the parsing code good,
// there is no proper standard for how servers should respond so this is what you get
pub fn parse_response(response: &str) -> Result<Server, Error> {
let json = Value::from_str(response)?;
let mut version = ();
let mut protocol = ();
// TODO! I think I can make this all better
if let Some(value) = json.get("version") {
if let Some(name) = json.get("name") {
version = version.as_str();
}
if let Some(value) = json.get("protocol") {
protocol = value.as_i64()
}
}
let protocol = json["version"]["protocol"].as_i64();
let icon = json["favicon"].to_string();
let prevents_reports = json["preventsReports"].as_bool();
let enforces_secure_chat = json["enforceSecure"].as_bool();
let motd: String;
if json["description"].is_object() {
// motd = parse_motd(json.get("description").clone(), &mut "".to_string());
} else {
motd = json.get("description").unwrap().to_string();
}
println!("{}", motd);
Ok(Server {
version: None,
protocol: None,
icon: None,
motd: None,
prevents_reports: None,
enforces_secure_chat,
mods: None,
players: None,
})
}
+27
View File
@@ -0,0 +1,27 @@
diesel::table! {
servers (id) {
id -> Integer,
address -> Text,
port -> Numeric,
firstseen -> Integer,
lastseen -> Integer,
country -> Text,
asn -> Text,
reversedns -> Text,
organization -> Text,
version -> Text,
protocol -> Integer,
fmlnetworkversion -> Integer,
motd -> Text,
icon -> Text,
timesseen -> Integer,
preventsreports -> Bool,
enforcesecure -> Bool,
whitelist -> Bool,
cracked -> Bool,
maxplayers -> Integer,
onlineplayers -> Integer,
#[sql_name = "type"]
software -> Text,
}
}