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 axum::{extract::Query, routing::get, Json, Router};
use mumble::{parse_version, ping_mumble_server};
use proto::PongPacket;
use mumble::{ServerInfo, ping_mumble_server};
mod proto;
mod mumble;
@ -32,38 +31,15 @@ struct PingOptions {
port: Option<u16>,
}
#[derive(serde::Serialize)]
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> {
async fn ping_server(Query(options): Query<PingOptions>) -> Result<Json<ServerInfo>, String> {
let tuple = (options.host, options.port.unwrap_or(64738));
let before = chrono::Utc::now().timestamp_micros() as u64;
match tuple.to_socket_addrs() {
Err(e) => Err(format!("invalid address: {e}")),
Ok(mut addrs) => match addrs.next() {
None => Err("could not resolve host".to_string()),
Some(addr) => match ping_mumble_server(addr).await {
Err(e) => Err(format!("could not ping server: {e}")),
Ok(pong) => {
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),
}))
},
Ok(info) => Ok(Json(info)),
}
}
}

View file

@ -4,29 +4,47 @@ use tokio::net::UdpSocket;
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
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 packet = PingPacket { id: 0, time: chrono::Utc::now().timestamp_micros() as u64 };
let buf = packet.serialize().await?;
socket.send_to(&buf, addr).await?;
let packet = PingPacket { action: 0, iden: chrono::Utc::now().timestamp_micros() as u64 };
let pkt = packet.serialize().await?;
let mut buf = [0u8; 64];
let before = chrono::Utc::now();
socket.send_to(&pkt, addr).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 {
format!(
"{}.{}.{}.{}",
version >> 24,
(version >> 16) & 255,
(version >> 8) & 255,
version & 255,
)
fn parse_version(version: u32) -> String {
let mut segments = Vec::new();
for b in version.to_be_bytes() {
if b != 255 {
segments.push(format!("{b}"));
}
}
segments.join(".")
}

View file

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