#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(clippy::needless_doctest_main)]
use std::{
fmt::Display,
io::{Read, Write},
};
mod entity;
#[cfg(feature = "async-futures")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-futures")))]
pub mod futures;
#[cfg(feature = "sync")]
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
pub mod sync;
#[cfg(feature = "async-tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
pub mod tokio;
pub use entity::*;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
UnsupportedProtocol,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(io) => io.fmt(f),
Self::UnsupportedProtocol => write!(f, "unsupported protocol"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
pub type Result<T> = std::result::Result<T, Error>;
fn build_latest_request(hostname: &str, port: u16) -> Result<Vec<u8>> {
let mut buffer = vec![
0x00, 0xff, 0xff, 0xff, 0xff,
0x0f, ];
write_varint(&mut buffer, hostname.len() as i32); buffer.extend_from_slice(hostname.as_bytes());
buffer.extend_from_slice(&[
(port >> 8) as u8,
(port & 0b1111_1111) as u8, 0x01, ]);
let mut full_buffer = vec![];
write_varint(&mut full_buffer, buffer.len() as i32); full_buffer.append(&mut buffer);
full_buffer.extend_from_slice(&[
1, 0x00, ]);
Ok(full_buffer)
}
fn decode_latest_response(buffer: &[u8]) -> Result<RawLatest> {
serde_json::from_slice(buffer).map_err(|_| Error::UnsupportedProtocol)
}
const LEGACY_REQUEST: [u8; 35] = [
0xfe, 0x01, 0xfa, 0x00, 0x0b, 0x00, 0x4d, 0x00, 0x43, 0x00, 0x7c, 0x00, 0x50, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x48,
0x00, 0x6f, 0x00, 0x73, 0x00, 0x74,
7, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
fn decode_legacy(buffer: &[u8]) -> Result<String> {
if buffer.len() <= 3 || buffer[0] != 0xff {
return Err(Error::UnsupportedProtocol);
}
let utf16be: Vec<u16> = buffer[3..]
.chunks_exact(2)
.map(|chunk| ((chunk[0] as u16) << 8) | chunk[1] as u16)
.collect();
String::from_utf16(&utf16be).map_err(|_| Error::UnsupportedProtocol)
}
fn parse_legacy(s: &str, raw: Vec<u8>) -> Result<Response> {
let mut fields = s.split('\0');
let magic = fields.next().map(|s| s == "\u{00a7}\u{0031}");
let protocol = fields.next().and_then(|s| s.parse().ok());
let version = fields.next();
let motd = fields.next();
let players = fields.next().and_then(|s| s.parse().ok());
let max_players = fields.next().and_then(|s| s.parse().ok());
match (magic, protocol, version, motd, players, max_players) {
(
Some(true),
Some(protocol),
Some(version),
Some(motd),
Some(players),
Some(max_players),
) => Ok(Response {
protocol,
enforces_secure_chat: None,
previews_chat: None,
version: version.to_string(),
description: Some(serde_json::Value::String(motd.to_string())),
online_players: players,
max_players,
favicon: None,
forge_data: None,
mod_info: None,
sample: None,
raw,
}),
_ => Err(Error::UnsupportedProtocol),
}
}
const LAST_SEVEN_BITS: i32 = 0b0111_1111;
const NEXT_BYTE_EXISTS: u8 = 0b1000_0000;
const SEVEN_BITS_SHIFT_MASK: i32 = 0x01_ff_ff_ff;
fn write_varint(sink: &mut Vec<u8>, mut value: i32) {
loop {
let mut temp = (value & LAST_SEVEN_BITS) as u8;
value >>= 7;
value &= SEVEN_BITS_SHIFT_MASK;
if value != 0 {
temp |= NEXT_BYTE_EXISTS;
}
sink.push(temp);
if value == 0 {
break;
}
}
}