[Crate] Add formatting (#22)

* chore: add standard for formatting

* chore: manually tidy up imports and format

* chore: remove vscode and add to gitignore

* chore: alphabetically order and fix

* chore: format

* chore: fix format issue with payload

* chore: format as merge had unformatted code

* [format] Fix comments, change max width and binop operator

---------

Co-authored-by: CosminPerRam <cosmin.p@live.com>
This commit is contained in:
Cain 2023-03-14 09:31:37 +01:00 committed by GitHub
parent e023e13236
commit 1b13d39856
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 3165 additions and 2593 deletions

View file

@ -1,9 +1,8 @@
/// The implementation.
pub mod protocol;
/// All types used by the implementation.
pub mod types;
pub use protocol::*;
pub use types::*;
pub use protocol::*;
/// The implementation.
pub mod protocol;
/// All types used by the implementation.
pub mod types;
pub use protocol::*;
pub use protocol::*;
pub use types::*;

View file

@ -1,101 +1,98 @@
/*
This file has code that has been documented by the NodeJS GameDig library (MIT) from
https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js
*/
use crate::GDResult;
use crate::bufferer::{Bufferer, Endianess};
use crate::GDError::{PacketBad, TypeParse};
use crate::protocols::minecraft::{BedrockResponse, GameMode, Server};
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, UdpSocket};
use crate::utils::error_by_expected_size;
pub struct Bedrock {
socket: UdpSocket
}
impl Bedrock {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self {
socket
})
}
fn send_status_request(&mut self) -> GDResult<()> {
self.socket.send(&[
// Message ID, ID_UNCONNECTED_PING
0x01,
// Nonce / timestamp
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
// Magic
0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78,
// Client GUID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?;
Ok(())
}
fn get_info(&mut self) -> GDResult<BedrockResponse> {
self.send_status_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
if buffer.get_u8()? != 0x1c {
return Err(PacketBad);
}
// Checking for our nonce directly from a u64 (as the nonce is 8 bytes).
if buffer.get_u64()? != 9833440827789222417 {
return Err(PacketBad);
}
// These 8 bytes are identical to the serverId string we receive in decimal below
buffer.move_position_ahead(8);
// Verifying the magic value (as we need 16 bytes, cast to two u64 values)
if buffer.get_u64()? != 18374403896610127616 {
return Err(PacketBad);
}
if buffer.get_u64()? != 8671175388723805693 {
return Err(PacketBad);
}
let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize;
buffer.move_position_ahead(2);
error_by_expected_size(remaining_length, buffer.remaining_length())?;
let binding = buffer.get_string_utf8_unended()?;
let status: Vec<&str> = binding.split(';').collect();
// We must have at least 6 values
if status.len() < 6 {
return Err(PacketBad);
}
Ok(BedrockResponse {
edition: status[0].to_string(),
name: status[1].to_string(),
version_name: status[3].to_string(),
version_protocol: status[2].to_string(),
players_maximum: status[5].parse().map_err(|_| TypeParse)?,
players_online: status[4].parse().map_err(|_| TypeParse)?,
id: status.get(6).map(|v| v.to_string()),
map: status.get(7).map(|v| v.to_string()),
game_mode: match status.get(8) {
None => None,
Some(v) => Some(GameMode::from_bedrock(v)?)
},
server_type: Server::Bedrock
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<BedrockResponse> {
Bedrock::new(address, port, timeout_settings)?.get_info()
}
}
// This file has code that has been documented by the NodeJS GameDig library
// (MIT) from https://github.com/gamedig/node-gamedig/blob/master/protocols/minecraftbedrock.js
use crate::{
bufferer::{Bufferer, Endianess},
protocols::{
minecraft::{BedrockResponse, GameMode, Server},
types::TimeoutSettings,
},
socket::{Socket, UdpSocket},
utils::error_by_expected_size,
GDError::{PacketBad, TypeParse},
GDResult,
};
pub struct Bedrock {
socket: UdpSocket,
}
impl Bedrock {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self { socket })
}
fn send_status_request(&mut self) -> GDResult<()> {
self.socket.send(&[
0x01, // Message ID: ID_UNCONNECTED_PING
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Nonce / timestamp
0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, // Magic
0x56, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Client GUID
])?;
Ok(())
}
fn get_info(&mut self) -> GDResult<BedrockResponse> {
self.send_status_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
if buffer.get_u8()? != 0x1c {
return Err(PacketBad);
}
// Checking for our nonce directly from a u64 (as the nonce is 8 bytes).
if buffer.get_u64()? != 9833440827789222417 {
return Err(PacketBad);
}
// These 8 bytes are identical to the serverId string we receive in decimal
// below
buffer.move_position_ahead(8);
// Verifying the magic value (as we need 16 bytes, cast to two u64 values)
if buffer.get_u64()? != 18374403896610127616 {
return Err(PacketBad);
}
if buffer.get_u64()? != 8671175388723805693 {
return Err(PacketBad);
}
let remaining_length = buffer.as_endianess(Endianess::Big).get_u16()? as usize;
buffer.move_position_ahead(2);
error_by_expected_size(remaining_length, buffer.remaining_length())?;
let binding = buffer.get_string_utf8_unended()?;
let status: Vec<&str> = binding.split(';').collect();
// We must have at least 6 values
if status.len() < 6 {
return Err(PacketBad);
}
Ok(BedrockResponse {
edition: status[0].to_string(),
name: status[1].to_string(),
version_name: status[3].to_string(),
version_protocol: status[2].to_string(),
players_maximum: status[5].parse().map_err(|_| TypeParse)?,
players_online: status[4].parse().map_err(|_| TypeParse)?,
id: status.get(6).map(|v| v.to_string()),
map: status.get(7).map(|v| v.to_string()),
game_mode: match status.get(8) {
None => None,
Some(v) => Some(GameMode::from_bedrock(v)?),
},
server_type: Server::Bedrock,
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<BedrockResponse> {
Bedrock::new(address, port, timeout_settings)?.get_info()
}
}

View file

@ -1,127 +1,144 @@
use serde_json::Value;
use crate::GDResult;
use crate::GDError::{JsonParse, PacketBad};
use crate::bufferer::{Bufferer, Endianess};
use crate::protocols::minecraft::{as_varint, get_string, get_varint, Player, JavaResponse, Server};
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, TcpSocket};
pub struct Java {
socket: TcpSocket
}
impl Java {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self {
socket
})
}
fn send(&mut self, data: Vec<u8>) -> GDResult<()> {
self.socket.send(&[as_varint(data.len() as i32), data].concat())
}
fn receive(&mut self) -> GDResult<Bufferer> {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
let _packet_length = get_varint(&mut buffer)? as usize;
//this declared 'packet length' from within the packet might be wrong (?), not checking with it...
Ok(buffer)
}
fn send_handshake(&mut self) -> GDResult<()> {
self.send([
//Packet ID (0)
0x00,
//Protocol Version (-1 to determine version)
0xFF, 0xFF, 0xFF, 0xFF, 0x0F,
//Server address (can be anything)
0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67,
//Server port (can be anything)
0x00, 0x00,
//Next state (1 for status)
0x01].to_vec())?;
Ok(())
}
fn send_status_request(&mut self) -> GDResult<()> {
self.send([
//Packet ID (0)
0x00].to_vec())?;
Ok(())
}
fn send_ping_request(&mut self) -> GDResult<()> {
self.send([
//Packet ID (1)
0x01].to_vec())?;
Ok(())
}
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_handshake()?;
self.send_status_request()?;
self.send_ping_request()?;
let mut buffer = self.receive()?;
if get_varint(&mut buffer)? != 0 { //first var int is the packet id
return Err(PacketBad);
}
let json_response = get_string(&mut buffer)?;
let value_response: Value = serde_json::from_str(&json_response)
.map_err(|_|JsonParse)?;
let version_name = value_response["version"]["name"].as_str()
.ok_or(PacketBad)?.to_string();
let version_protocol = value_response["version"]["protocol"].as_i64()
.ok_or(PacketBad)? as i32;
let max_players = value_response["players"]["max"].as_u64()
.ok_or(PacketBad)? as u32;
let online_players = value_response["players"]["online"].as_u64()
.ok_or(PacketBad)? as u32;
let sample_players: Option<Vec<Player>> = match value_response["players"]["sample"].is_null() {
true => None,
false => Some({
let players_values = value_response["players"]["sample"].as_array()
.ok_or(PacketBad)?;
let mut players = Vec::with_capacity(players_values.len());
for player in players_values {
players.push(Player {
name: player["name"].as_str().ok_or(PacketBad)?.to_string(),
id: player["id"].as_str().ok_or(PacketBad)?.to_string()
})
}
players
})
};
Ok(JavaResponse {
version_name,
version_protocol,
players_maximum: max_players,
players_online: online_players,
players_sample: sample_players,
description: value_response["description"].to_string(),
favicon: value_response["favicon"].as_str().map(str::to_string),
previews_chat: value_response["previewsChat"].as_bool(),
enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(),
server_type: Server::Java
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
Java::new(address, port, timeout_settings)?.get_info()
}
}
use crate::{
bufferer::{Bufferer, Endianess},
protocols::{
minecraft::{as_varint, get_string, get_varint, JavaResponse, Player, Server},
types::TimeoutSettings,
},
socket::{Socket, TcpSocket},
GDError::{JsonParse, PacketBad},
GDResult,
};
use serde_json::Value;
#[rustfmt::skip]
const PAYLOAD: [u8; 17] = [
//Packet ID (0)
0x00,
//Protocol Version (-1 to determine version)
0xFF, 0xFF, 0xFF, 0xFF, 0x0F,
//Server address (can be anything)
0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67,
//Server port (can be anything)
0x00, 0x00,
//Next state (1 for status)
0x01
];
pub struct Java {
socket: TcpSocket,
}
impl Java {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self { socket })
}
fn send(&mut self, data: Vec<u8>) -> GDResult<()> {
self.socket
.send(&[as_varint(data.len() as i32), data].concat())
}
fn receive(&mut self) -> GDResult<Bufferer> {
let mut buffer = Bufferer::new_with_data(Endianess::Little, &self.socket.receive(None)?);
let _packet_length = get_varint(&mut buffer)? as usize;
// this declared 'packet length' from within the packet might be wrong (?), not
// checking with it...
Ok(buffer)
}
fn send_handshake(&mut self) -> GDResult<()> {
self.send(PAYLOAD.to_vec())?;
Ok(())
}
fn send_status_request(&mut self) -> GDResult<()> {
self.send(
[0x00] // Packet ID (0)
.to_vec(),
)?;
Ok(())
}
fn send_ping_request(&mut self) -> GDResult<()> {
self.send(
[0x01] // Packet ID (1)
.to_vec(),
)?;
Ok(())
}
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_handshake()?;
self.send_status_request()?;
self.send_ping_request()?;
let mut buffer = self.receive()?;
if get_varint(&mut buffer)? != 0 {
// first var int is the packet id
return Err(PacketBad);
}
let json_response = get_string(&mut buffer)?;
let value_response: Value = serde_json::from_str(&json_response).map_err(|_| JsonParse)?;
let version_name = value_response["version"]["name"]
.as_str()
.ok_or(PacketBad)?
.to_string();
let version_protocol = value_response["version"]["protocol"]
.as_i64()
.ok_or(PacketBad)? as i32;
let max_players = value_response["players"]["max"].as_u64().ok_or(PacketBad)? as u32;
let online_players = value_response["players"]["online"]
.as_u64()
.ok_or(PacketBad)? as u32;
let sample_players: Option<Vec<Player>> = match value_response["players"]["sample"].is_null() {
true => None,
false => {
Some({
let players_values = value_response["players"]["sample"]
.as_array()
.ok_or(PacketBad)?;
let mut players = Vec::with_capacity(players_values.len());
for player in players_values {
players.push(Player {
name: player["name"].as_str().ok_or(PacketBad)?.to_string(),
id: player["id"].as_str().ok_or(PacketBad)?.to_string(),
})
}
players
})
}
};
Ok(JavaResponse {
version_name,
version_protocol,
players_maximum: max_players,
players_online: online_players,
players_sample: sample_players,
description: value_response["description"].to_string(),
favicon: value_response["favicon"].as_str().map(str::to_string),
previews_chat: value_response["previewsChat"].as_bool(),
enforces_secure_chat: value_response["enforcesSecureChat"].as_bool(),
server_type: Server::Java,
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
Java::new(address, port, timeout_settings)?.get_info()
}
}

View file

@ -1,68 +1,65 @@
use crate::GDResult;
use crate::bufferer::{Bufferer, Endianess};
use crate::GDError::{PacketBad, ProtocolFormat};
use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server};
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, TcpSocket};
use crate::utils::error_by_expected_size;
pub struct LegacyBV1_8 {
socket: TcpSocket
}
impl LegacyBV1_8 {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self {
socket
})
}
fn send_initial_request(&mut self) -> GDResult<()> {
self.socket.send(&[0xFE])
}
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_initial_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?);
if buffer.get_u8()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
error_by_expected_size((length + 3) as usize, buffer.data_length())?;
let packet_string = buffer.get_string_utf16()?;
let split: Vec<&str> = packet_string.split('§').collect();
error_by_expected_size(3, split.len())?;
let description = split[0].to_string();
let online_players = split[1].parse()
.map_err(|_| PacketBad)?;
let max_players = split[2].parse()
.map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name: "Beta 1.8+".to_string(),
version_protocol: -1,
players_maximum: max_players,
players_online: online_players,
players_sample: None,
description,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Legacy(LegacyGroup::VB1_8)
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
LegacyBV1_8::new(address, port, timeout_settings)?.get_info()
}
}
use crate::{
bufferer::{Bufferer, Endianess},
protocols::{
minecraft::{JavaResponse, LegacyGroup, Server},
types::TimeoutSettings,
},
socket::{Socket, TcpSocket},
utils::error_by_expected_size,
GDError::{PacketBad, ProtocolFormat},
GDResult,
};
pub struct LegacyBV1_8 {
socket: TcpSocket,
}
impl LegacyBV1_8 {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self { socket })
}
fn send_initial_request(&mut self) -> GDResult<()> { self.socket.send(&[0xFE]) }
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_initial_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?);
if buffer.get_u8()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
error_by_expected_size((length + 3) as usize, buffer.data_length())?;
let packet_string = buffer.get_string_utf16()?;
let split: Vec<&str> = packet_string.split('§').collect();
error_by_expected_size(3, split.len())?;
let description = split[0].to_string();
let online_players = split[1].parse().map_err(|_| PacketBad)?;
let max_players = split[2].parse().map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name: "Beta 1.8+".to_string(),
version_protocol: -1,
players_maximum: max_players,
players_online: online_players,
players_sample: None,
description,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Legacy(LegacyGroup::VB1_8),
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
LegacyBV1_8::new(address, port, timeout_settings)?.get_info()
}
}

View file

@ -1,73 +1,69 @@
use crate::GDResult;
use crate::bufferer::{Bufferer, Endianess};
use crate::GDError::{PacketBad, ProtocolFormat};
use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server};
use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6;
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, TcpSocket};
use crate::utils::error_by_expected_size;
pub struct LegacyV1_4 {
socket: TcpSocket
}
impl LegacyV1_4 {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self {
socket
})
}
fn send_initial_request(&mut self) -> GDResult<()> {
self.socket.send(&[0xFE, 0x01])
}
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_initial_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?);
if buffer.get_u8()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
error_by_expected_size((length + 3) as usize, buffer.data_length())?;
if LegacyV1_6::is_protocol(&mut buffer)? {
return LegacyV1_6::get_response(&mut buffer);
}
let packet_string = buffer.get_string_utf16()?;
let split: Vec<&str> = packet_string.split('§').collect();
error_by_expected_size(3, split.len())?;
let description = split[0].to_string();
let online_players = split[1].parse()
.map_err(|_| PacketBad)?;
let max_players = split[2].parse()
.map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name: "1.4+".to_string(),
version_protocol: -1,
players_maximum: max_players,
players_online: online_players,
players_sample: None,
description,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Legacy(LegacyGroup::V1_4)
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
LegacyV1_4::new(address, port, timeout_settings)?.get_info()
}
}
use crate::{
bufferer::{Bufferer, Endianess},
protocols::{
minecraft::{protocol::legacy_v1_6::LegacyV1_6, JavaResponse, LegacyGroup, Server},
types::TimeoutSettings,
},
socket::{Socket, TcpSocket},
utils::error_by_expected_size,
GDError::{PacketBad, ProtocolFormat},
GDResult,
};
pub struct LegacyV1_4 {
socket: TcpSocket,
}
impl LegacyV1_4 {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self { socket })
}
fn send_initial_request(&mut self) -> GDResult<()> { self.socket.send(&[0xFE, 0x01]) }
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_initial_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?);
if buffer.get_u8()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
error_by_expected_size((length + 3) as usize, buffer.data_length())?;
if LegacyV1_6::is_protocol(&mut buffer)? {
return LegacyV1_6::get_response(&mut buffer);
}
let packet_string = buffer.get_string_utf16()?;
let split: Vec<&str> = packet_string.split('§').collect();
error_by_expected_size(3, split.len())?;
let description = split[0].to_string();
let online_players = split[1].parse().map_err(|_| PacketBad)?;
let max_players = split[2].parse().map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name: "1.4+".to_string(),
version_protocol: -1,
players_maximum: max_players,
players_online: online_players,
players_sample: None,
description,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Legacy(LegacyGroup::V1_4),
})
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
LegacyV1_4::new(address, port, timeout_settings)?.get_info()
}
}

View file

@ -1,100 +1,98 @@
use crate::GDResult;
use crate::GDError::{PacketBad, ProtocolFormat};
use crate::bufferer::{Bufferer, Endianess};
use crate::protocols::minecraft::{LegacyGroup, JavaResponse, Server};
use crate::protocols::types::TimeoutSettings;
use crate::socket::{Socket, TcpSocket};
use crate::utils::error_by_expected_size;
pub struct LegacyV1_6 {
socket: TcpSocket
}
impl LegacyV1_6 {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self {
socket
})
}
fn send_initial_request(&mut self) -> GDResult<()> {
self.socket.send(&[
// Packet ID (FE)
0xfe,
// Ping payload (01)
0x01,
// Packet identifier for plugin message
0xfa,
// Length of 'GameDig' string (7) as unsigned short
0x00, 0x07,
// 'GameDig' string as UTF-16BE
0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67])?;
Ok(())
}
pub fn is_protocol(buffer: &mut Bufferer) -> GDResult<bool> {
let state = buffer.remaining_data().starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]);
if state {
buffer.move_position_ahead(6);
}
Ok(state)
}
pub fn get_response(buffer: &mut Bufferer) -> GDResult<JavaResponse> {
let packet_string = buffer.get_string_utf16()?;
let split: Vec<&str> = packet_string.split('\x00').collect();
error_by_expected_size(5, split.len())?;
let version_protocol = split[0].parse()
.map_err(|_| PacketBad)?;
let version_name = split[1].to_string();
let description = split[2].to_string();
let online_players = split[3].parse()
.map_err(|_| PacketBad)?;
let max_players = split[4].parse()
.map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name,
version_protocol,
players_maximum: max_players,
players_online: online_players,
players_sample: None,
description,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Legacy(LegacyGroup::V1_6)
})
}
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_initial_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?);
if buffer.get_u8()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
error_by_expected_size((length + 3) as usize, buffer.data_length())?;
if !LegacyV1_6::is_protocol(&mut buffer)? {
return Err(ProtocolFormat);
}
LegacyV1_6::get_response(&mut buffer)
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
LegacyV1_6::new(address, port, timeout_settings)?.get_info()
}
}
use crate::{
bufferer::{Bufferer, Endianess},
protocols::{
minecraft::{JavaResponse, LegacyGroup, Server},
types::TimeoutSettings,
},
socket::{Socket, TcpSocket},
utils::error_by_expected_size,
GDError::{PacketBad, ProtocolFormat},
GDResult,
};
pub struct LegacyV1_6 {
socket: TcpSocket,
}
impl LegacyV1_6 {
fn new(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address, port)?;
socket.apply_timeout(timeout_settings)?;
Ok(Self { socket })
}
fn send_initial_request(&mut self) -> GDResult<()> {
self.socket.send(&[
0xfe, // Packet ID (FE)
0x01, // Ping payload (01)
0xfa, // Packet identifier for plugin message
0x00, 0x07, // Length of 'GameDig' string (7) as unsigned short
0x00, 0x47, 0x00, 0x61, 0x00, 0x6D, 0x00, 0x65, 0x00, 0x44, 0x00, 0x69, 0x00,
0x67, // 'GameDig' string as UTF-16BE
])?;
Ok(())
}
pub fn is_protocol(buffer: &mut Bufferer) -> GDResult<bool> {
let state = buffer
.remaining_data()
.starts_with(&[0x00, 0xA7, 0x00, 0x31, 0x00, 0x00]);
if state {
buffer.move_position_ahead(6);
}
Ok(state)
}
pub fn get_response(buffer: &mut Bufferer) -> GDResult<JavaResponse> {
let packet_string = buffer.get_string_utf16()?;
let split: Vec<&str> = packet_string.split('\x00').collect();
error_by_expected_size(5, split.len())?;
let version_protocol = split[0].parse().map_err(|_| PacketBad)?;
let version_name = split[1].to_string();
let description = split[2].to_string();
let online_players = split[3].parse().map_err(|_| PacketBad)?;
let max_players = split[4].parse().map_err(|_| PacketBad)?;
Ok(JavaResponse {
version_name,
version_protocol,
players_maximum: max_players,
players_online: online_players,
players_sample: None,
description,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Legacy(LegacyGroup::V1_6),
})
}
fn get_info(&mut self) -> GDResult<JavaResponse> {
self.send_initial_request()?;
let mut buffer = Bufferer::new_with_data(Endianess::Big, &self.socket.receive(None)?);
if buffer.get_u8()? != 0xFF {
return Err(ProtocolFormat);
}
let length = buffer.get_u16()? * 2;
error_by_expected_size((length + 3) as usize, buffer.data_length())?;
if !LegacyV1_6::is_protocol(&mut buffer)? {
return Err(ProtocolFormat);
}
LegacyV1_6::get_response(&mut buffer)
}
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
LegacyV1_6::new(address, port, timeout_settings)?.get_info()
}
}

View file

@ -1,68 +1,82 @@
use crate::GDError::AutoQuery;
use crate::GDResult;
use crate::protocols::minecraft::{BedrockResponse, LegacyGroup, JavaResponse};
use crate::protocols::minecraft::protocol::bedrock::Bedrock;
use crate::protocols::minecraft::protocol::java::Java;
use crate::protocols::minecraft::protocol::legacy_v1_4::LegacyV1_4;
use crate::protocols::minecraft::protocol::legacy_v1_6::LegacyV1_6;
use crate::protocols::minecraft::protocol::legacy_bv1_8::LegacyBV1_8;
use crate::protocols::types::TimeoutSettings;
mod java;
mod legacy_v1_4;
mod legacy_v1_6;
mod legacy_bv1_8;
mod bedrock;
/// Queries a Minecraft server with all the protocol variants one by one (Java -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)).
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
if let Ok(response) = query_java(address, port, timeout_settings.clone()) {
return Ok(response);
}
if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) {
return Ok(JavaResponse::from_bedrock_response(response));
}
if let Ok(response) = query_legacy(address, port, timeout_settings) {
return Ok(response);
}
Err(AutoQuery)
}
/// Query a Java Server.
pub fn query_java(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
Java::query(address, port, timeout_settings)
}
/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8).
pub fn query_legacy(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) {
return Ok(response);
}
if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) {
return Ok(response);
}
if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) {
return Ok(response);
}
Err(AutoQuery)
}
/// Query a specific (Java) Legacy Server.
pub fn query_legacy_specific(group: LegacyGroup, address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
match group {
LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings),
LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings),
LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings)
}
}
/// Query a Bedrock Server.
pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<BedrockResponse> {
Bedrock::query(address, port, timeout_settings)
}
use crate::{
protocols::minecraft::{
protocol::{
bedrock::Bedrock,
java::Java,
legacy_bv1_8::LegacyBV1_8,
legacy_v1_4::LegacyV1_4,
legacy_v1_6::LegacyV1_6,
},
BedrockResponse,
JavaResponse,
LegacyGroup,
},
protocols::types::TimeoutSettings,
GDError::AutoQuery,
GDResult,
};
mod bedrock;
mod java;
mod legacy_bv1_8;
mod legacy_v1_4;
mod legacy_v1_6;
/// Queries a Minecraft server with all the protocol variants one by one (Java
/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)).
pub fn query(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
if let Ok(response) = query_java(address, port, timeout_settings.clone()) {
return Ok(response);
}
if let Ok(response) = query_bedrock(address, port, timeout_settings.clone()) {
return Ok(JavaResponse::from_bedrock_response(response));
}
if let Ok(response) = query_legacy(address, port, timeout_settings) {
return Ok(response);
}
Err(AutoQuery)
}
/// Query a Java Server.
pub fn query_java(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
Java::query(address, port, timeout_settings)
}
/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8).
pub fn query_legacy(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
if let Ok(response) = query_legacy_specific(LegacyGroup::V1_6, address, port, timeout_settings.clone()) {
return Ok(response);
}
if let Ok(response) = query_legacy_specific(LegacyGroup::V1_4, address, port, timeout_settings.clone()) {
return Ok(response);
}
if let Ok(response) = query_legacy_specific(LegacyGroup::VB1_8, address, port, timeout_settings) {
return Ok(response);
}
Err(AutoQuery)
}
/// Query a specific (Java) Legacy Server.
pub fn query_legacy_specific(
group: LegacyGroup,
address: &str,
port: u16,
timeout_settings: Option<TimeoutSettings>,
) -> GDResult<JavaResponse> {
match group {
LegacyGroup::V1_6 => LegacyV1_6::query(address, port, timeout_settings),
LegacyGroup::V1_4 => LegacyV1_4::query(address, port, timeout_settings),
LegacyGroup::VB1_8 => LegacyBV1_8::query(address, port, timeout_settings),
}
}
/// Query a Bedrock Server.
pub fn query_bedrock(address: &str, port: u16, timeout_settings: Option<TimeoutSettings>) -> GDResult<BedrockResponse> {
Bedrock::query(address, port, timeout_settings)
}

View file

@ -1,204 +1,205 @@
/*
Although its a lightly modified version, this file contains code
by Jaiden Bernard (2021-2022 - MIT) from
https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs
*/
use crate::bufferer::Bufferer;
use crate::GDError::{PacketBad, UnknownEnumCast};
use crate::GDResult;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// The type of Minecraft Server you want to query.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Server {
/// Java Edition.
Java,
/// Legacy Java.
Legacy(LegacyGroup),
/// Bedrock Edition.
Bedrock,
}
/// Legacy Java (Versions) Groups.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum LegacyGroup {
/// 1.6
V1_6,
/// 1.4 - 1.5
V1_4,
/// Beta 1.8 - 1.3
VB1_8,
}
/// Information about a player.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Player {
pub name: String,
pub id: String,
}
/// A Java query response.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct JavaResponse {
/// Version name, example: "1.19.2".
pub version_name: String,
/// Version protocol, example: 760 (for 1.19.2). Note that for versions below 1.6 this field is always -1.
pub version_protocol: i32,
/// Number of server capacity.
pub players_maximum: u32,
/// Number of online players.
pub players_online: u32,
/// Some online players (can be missing).
pub players_sample: Option<Vec<Player>>,
/// Server's description or MOTD.
pub description: String,
/// The favicon (can be missing).
pub favicon: Option<String>,
/// Tells if the chat preview is enabled (can be missing).
pub previews_chat: Option<bool>,
/// Tells if secure chat is enforced (can be missing).
pub enforces_secure_chat: Option<bool>,
/// Tell's the server type.
pub server_type: Server,
}
/// A Bedrock Edition query response.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BedrockResponse {
/// Server's edition.
pub edition: String,
/// Server's name.
pub name: String,
/// Version name, example: "1.19.40".
pub version_name: String,
/// Version protocol, example: 760 (for 1.19.2).
pub version_protocol: String,
/// Maximum number of players the server reports it can hold.
pub players_maximum: u32,
/// Number of players on the server.
pub players_online: u32,
/// Server id.
pub id: Option<String>,
/// Currently running map's name.
pub map: Option<String>,
/// Current game mode.
pub game_mode: Option<GameMode>,
/// Tells the server type.
pub server_type: Server,
}
impl JavaResponse {
pub fn from_bedrock_response(response: BedrockResponse) -> Self {
Self {
version_name: response.version_name,
version_protocol: 0,
players_maximum: response.players_maximum,
players_online: response.players_online,
players_sample: None,
description: response.name,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Bedrock,
}
}
}
/// A server's game mode (used only by Bedrock servers.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GameMode {
Survival,
Creative,
Hardcore,
Spectator,
Adventure,
}
impl GameMode {
pub fn from_bedrock(value: &&str) -> GDResult<Self> {
match *value {
"Survival" => Ok(GameMode::Survival),
"Creative" => Ok(GameMode::Creative),
"Hardcore" => Ok(GameMode::Hardcore),
"Spectator" => Ok(GameMode::Spectator),
"Adventure" => Ok(GameMode::Adventure),
_ => Err(UnknownEnumCast),
}
}
}
pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult<i32> {
let mut result = 0;
let msb: u8 = 0b10000000;
let mask: u8 = !msb;
for i in 0..5 {
let current_byte = buffer.get_u8()?;
result |= ((current_byte & mask) as i32) << (7 * i);
// The 5th byte is only allowed to have the 4 smallest bits set
if i == 4 && (current_byte & 0xf0 != 0) {
return Err(PacketBad);
}
if (current_byte & msb) == 0 {
break;
}
}
Ok(result)
}
pub(crate) fn as_varint(value: i32) -> Vec<u8> {
let mut bytes = vec![];
let mut reading_value = value;
let msb: u8 = 0b10000000;
let mask: i32 = 0b01111111;
for _ in 0..5 {
let tmp = (reading_value & mask) as u8;
reading_value &= !mask;
reading_value = reading_value.rotate_right(7);
if reading_value != 0 {
bytes.push(tmp | msb);
} else {
bytes.push(tmp);
break;
}
}
bytes
}
pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult<String> {
let length = get_varint(buffer)? as usize;
let mut text = Vec::with_capacity(length);
for _ in 0..length {
text.push(buffer.get_u8()?)
}
String::from_utf8(text).map_err(|_| PacketBad)
}
#[allow(dead_code)]
pub(crate) fn as_string(value: String) -> Vec<u8> {
let mut buf = as_varint(value.len() as i32);
buf.extend(value.as_bytes().to_vec());
buf
}
// Although its a lightly modified version, this file contains code
// by Jaiden Bernard (2021-2022 - MIT) from
// https://github.com/thisjaiden/golden_apple/blob/master/src/lib.rs
use crate::{
bufferer::Bufferer,
GDError::{PacketBad, UnknownEnumCast},
GDResult,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// The type of Minecraft Server you want to query.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Server {
/// Java Edition.
Java,
/// Legacy Java.
Legacy(LegacyGroup),
/// Bedrock Edition.
Bedrock,
}
/// Legacy Java (Versions) Groups.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum LegacyGroup {
/// 1.6
V1_6,
/// 1.4 - 1.5
V1_4,
/// Beta 1.8 - 1.3
VB1_8,
}
/// Information about a player.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Player {
pub name: String,
pub id: String,
}
/// A Java query response.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct JavaResponse {
/// Version name, example: "1.19.2".
pub version_name: String,
/// Version protocol, example: 760 (for 1.19.2). Note that for versions
/// below 1.6 this field is always -1.
pub version_protocol: i32,
/// Number of server capacity.
pub players_maximum: u32,
/// Number of online players.
pub players_online: u32,
/// Some online players (can be missing).
pub players_sample: Option<Vec<Player>>,
/// Server's description or MOTD.
pub description: String,
/// The favicon (can be missing).
pub favicon: Option<String>,
/// Tells if the chat preview is enabled (can be missing).
pub previews_chat: Option<bool>,
/// Tells if secure chat is enforced (can be missing).
pub enforces_secure_chat: Option<bool>,
/// Tell's the server type.
pub server_type: Server,
}
/// A Bedrock Edition query response.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BedrockResponse {
/// Server's edition.
pub edition: String,
/// Server's name.
pub name: String,
/// Version name, example: "1.19.40".
pub version_name: String,
/// Version protocol, example: 760 (for 1.19.2).
pub version_protocol: String,
/// Maximum number of players the server reports it can hold.
pub players_maximum: u32,
/// Number of players on the server.
pub players_online: u32,
/// Server id.
pub id: Option<String>,
/// Currently running map's name.
pub map: Option<String>,
/// Current game mode.
pub game_mode: Option<GameMode>,
/// Tells the server type.
pub server_type: Server,
}
impl JavaResponse {
pub fn from_bedrock_response(response: BedrockResponse) -> Self {
Self {
version_name: response.version_name,
version_protocol: 0,
players_maximum: response.players_maximum,
players_online: response.players_online,
players_sample: None,
description: response.name,
favicon: None,
previews_chat: None,
enforces_secure_chat: None,
server_type: Server::Bedrock,
}
}
}
/// A server's game mode (used only by Bedrock servers.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GameMode {
Survival,
Creative,
Hardcore,
Spectator,
Adventure,
}
impl GameMode {
pub fn from_bedrock(value: &&str) -> GDResult<Self> {
match *value {
"Survival" => Ok(GameMode::Survival),
"Creative" => Ok(GameMode::Creative),
"Hardcore" => Ok(GameMode::Hardcore),
"Spectator" => Ok(GameMode::Spectator),
"Adventure" => Ok(GameMode::Adventure),
_ => Err(UnknownEnumCast),
}
}
}
pub(crate) fn get_varint(buffer: &mut Bufferer) -> GDResult<i32> {
let mut result = 0;
let msb: u8 = 0b10000000;
let mask: u8 = !msb;
for i in 0 .. 5 {
let current_byte = buffer.get_u8()?;
result |= ((current_byte & mask) as i32) << (7 * i);
// The 5th byte is only allowed to have the 4 smallest bits set
if i == 4 && (current_byte & 0xf0 != 0) {
return Err(PacketBad);
}
if (current_byte & msb) == 0 {
break;
}
}
Ok(result)
}
pub(crate) fn as_varint(value: i32) -> Vec<u8> {
let mut bytes = vec![];
let mut reading_value = value;
let msb: u8 = 0b10000000;
let mask: i32 = 0b01111111;
for _ in 0 .. 5 {
let tmp = (reading_value & mask) as u8;
reading_value &= !mask;
reading_value = reading_value.rotate_right(7);
if reading_value != 0 {
bytes.push(tmp | msb);
} else {
bytes.push(tmp);
break;
}
}
bytes
}
pub(crate) fn get_string(buffer: &mut Bufferer) -> GDResult<String> {
let length = get_varint(buffer)? as usize;
let mut text = Vec::with_capacity(length);
for _ in 0 .. length {
text.push(buffer.get_u8()?)
}
String::from_utf8(text).map_err(|_| PacketBad)
}
#[allow(dead_code)]
pub(crate) fn as_string(value: String) -> Vec<u8> {
let mut buf = as_varint(value.len() as i32);
buf.extend(value.as_bytes().to_vec());
buf
}