feat: better latency and server info struct

This commit is contained in:
əlemi 2024-02-17 03:13:43 +01:00
parent 018bd76718
commit 734de0bde5
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 43 additions and 49 deletions

View file

@ -3,8 +3,7 @@ use std::net::ToSocketAddrs;
use clap::Parser; use clap::Parser;
use axum::{extract::Query, routing::get, Json, Router}; use axum::{extract::Query, routing::get, Json, Router};
use mumble::{parse_version, ping_mumble_server}; use mumble::{ServerInfo, ping_mumble_server};
use proto::PongPacket;
mod proto; mod proto;
mod mumble; mod mumble;
@ -32,38 +31,15 @@ struct PingOptions {
port: Option<u16>, port: Option<u16>,
} }
#[derive(serde::Serialize)] async fn ping_server(Query(options): Query<PingOptions>) -> Result<Json<ServerInfo>, String> {
struct PongResponse {
version: String,
users: i32,
max_users: i32,
bandwidth: String,
time: u64,
ping: u64,
roundtrip: u64,
}
async fn ping_server(Query(options): Query<PingOptions>) -> Result<Json<PongResponse>, String> {
let tuple = (options.host, options.port.unwrap_or(64738)); let tuple = (options.host, options.port.unwrap_or(64738));
let before = chrono::Utc::now().timestamp_micros() as u64;
match tuple.to_socket_addrs() { match tuple.to_socket_addrs() {
Err(e) => Err(format!("invalid address: {e}")), Err(e) => Err(format!("invalid address: {e}")),
Ok(mut addrs) => match addrs.next() { Ok(mut addrs) => match addrs.next() {
None => Err("could not resolve host".to_string()), None => Err("could not resolve host".to_string()),
Some(addr) => match ping_mumble_server(addr).await { Some(addr) => match ping_mumble_server(addr).await {
Err(e) => Err(format!("could not ping server: {e}")), Err(e) => Err(format!("could not ping server: {e}")),
Ok(pong) => { Ok(info) => Ok(Json(info)),
let after = chrono::Utc::now().timestamp_micros() as u64;
Ok(Json(PongResponse {
users: pong.users,
max_users: pong.max_users,
bandwidth: format!("{}kbit/s", pong.bandwidth / 1000),
time: pong.time,
ping: pong.time - before,
roundtrip: after - before,
version: parse_version(pong.version),
}))
},
} }
} }
} }

View file

@ -4,29 +4,47 @@ use tokio::net::UdpSocket;
use crate::proto::{PingPacket, PongPacket}; use crate::proto::{PingPacket, PongPacket};
#[derive(serde::Serialize)]
pub struct ServerInfo {
pub version: String,
pub users: i32,
pub max_users: i32,
pub bandwidth: i32,
pub latency: i64,
}
pub async fn ping_mumble_server(addr: SocketAddr) -> std::io::Result<PongPacket> { pub async fn ping_mumble_server(addr: SocketAddr) -> std::io::Result<ServerInfo> {
// by default bind on any interface and request OS to give us a port // by default bind on any interface and request OS to give us a port
let from_addr : SocketAddr = "0.0.0.0:0".parse().expect("could not create socketaddr from '0.0.0.0:0'"); let from_addr : SocketAddr = "0.0.0.0:0".parse().expect("could not create socketaddr from '0.0.0.0:0'");
let socket = UdpSocket::bind(from_addr).await?; let socket = UdpSocket::bind(from_addr).await?;
let packet = PingPacket { id: 0, time: chrono::Utc::now().timestamp_micros() as u64 }; let packet = PingPacket { action: 0, iden: chrono::Utc::now().timestamp_micros() as u64 };
let buf = packet.serialize().await?; let pkt = packet.serialize().await?;
socket.send_to(&buf, addr).await?;
let mut buf = [0u8; 64]; let mut buf = [0u8; 64];
let before = chrono::Utc::now();
socket.send_to(&pkt, addr).await?;
socket.recv(&mut buf).await?; socket.recv(&mut buf).await?;
let latency = chrono::Utc::now() - before;
PongPacket::deserialize(&buf).await let pong = PongPacket::deserialize(&buf).await?;
Ok(ServerInfo {
version: parse_version(pong.version),
users: pong.users,
max_users: pong.max_users,
bandwidth: pong.bandwidth,
latency: latency.num_milliseconds(),
})
} }
pub fn parse_version(version: u32) -> String { fn parse_version(version: u32) -> String {
format!( let mut segments = Vec::new();
"{}.{}.{}.{}", for b in version.to_be_bytes() {
version >> 24, if b != 255 {
(version >> 16) & 255, segments.push(format!("{b}"));
(version >> 8) & 255, }
version & 255, }
) segments.join(".")
} }

View file

@ -1,15 +1,15 @@
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub struct PingPacket { pub struct PingPacket {
pub id: i32, pub action: i32,
pub time: u64, pub iden: u64,
} }
impl PingPacket { impl PingPacket {
pub async fn serialize(self) -> std::io::Result<Vec<u8>> { pub async fn serialize(self) -> std::io::Result<Vec<u8>> {
let mut out = Vec::new(); let mut out = Vec::new();
out.write_i32(self.id).await?; out.write_i32(self.action).await?;
out.write_u64(self.time).await?; out.write_u64(self.iden).await?;
Ok(out) Ok(out)
} }
} }
@ -17,7 +17,7 @@ impl PingPacket {
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
pub struct PongPacket { pub struct PongPacket {
pub version: u32, pub version: u32,
pub time: u64, pub iden: u64,
pub users: i32, pub users: i32,
pub max_users: i32, pub max_users: i32,
pub bandwidth: i32, pub bandwidth: i32,
@ -26,10 +26,10 @@ pub struct PongPacket {
impl PongPacket { impl PongPacket {
pub async fn deserialize(mut data: &[u8]) -> std::io::Result<Self> { pub async fn deserialize(mut data: &[u8]) -> std::io::Result<Self> {
let version = data.read_u32().await?; let version = data.read_u32().await?;
let time = data.read_u64().await?; let iden = data.read_u64().await?;
let users = data.read_i32().await?; let users = data.read_i32().await?;
let max_users = data.read_i32().await?; let max_users = data.read_i32().await?;
let bandwidth = data.read_i32().await?; let bandwidth = data.read_i32().await?;
Ok(PongPacket { version, time, users, max_users, bandwidth }) Ok(PongPacket { version, iden, users, max_users, bandwidth })
} }
} }