Move mumble UDP ping into common crate (#33)
The desktop GUI doesn't go through the proxy to reach the Mumble server, so its login screen needs to ping directly. Rather than duplicate the ping logic, move it into the common crate behind an optional `networking` feature (so common stays lightweight when the feature isn't requested). - common: add `ping_server(address, port)` behind `networking` feature - proxy: replace the inline UdpSocket ping + ping.rs codec with a call to common::ping_server; drop the rand dep - client: PlatformInterface::get_status now takes an address; desktop and mobile call common::ping_server directly, web still uses the proxy's /status endpoint - gui: thread the address from the login input through get_status, so it re-pings when the user edits the address Assisted-by: claude-opus-4-7 Reviewed-on: #33 Reviewed-by: restitux <restitux@ohea.xyz> Co-authored-by: Sam Sartor <me@samsartor.com> Co-committed-by: Sam Sartor <me@samsartor.com>
This commit was merged in pull request #33.
This commit is contained in:
@@ -3,5 +3,10 @@ name = "mumble-web2-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
networking = ["dep:tokio", "dep:color-eyre"]
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
tokio = { version = "1", features = ["net", "time"], optional = true }
|
||||
color-eyre = { version = "0.6", optional = true }
|
||||
|
||||
@@ -16,3 +16,62 @@ pub struct ServerStatus {
|
||||
pub max_users: Option<u32>,
|
||||
pub bandwidth: Option<u32>,
|
||||
}
|
||||
|
||||
/// Mumble UDP ping protocol.
|
||||
///
|
||||
/// Send a 12-byte packet: 4 zero bytes + 8-byte identifier.
|
||||
/// Receive a 24-byte response: 4 bytes version + 8 bytes identifier echo
|
||||
/// + 4 bytes current_users + 4 bytes max_users + 4 bytes bandwidth.
|
||||
#[cfg(feature = "networking")]
|
||||
pub async fn ping_server(address: &str, port: u16) -> color_eyre::Result<ServerStatus> {
|
||||
use color_eyre::eyre::{bail, eyre};
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::time::Duration;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
let dest = format!("{}:{}", address, port)
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or_else(|| eyre!("could not resolve address"))?;
|
||||
|
||||
let bind_addr = if dest.is_ipv6() { "[::]:0" } else { "0.0.0.0:0" };
|
||||
let socket = UdpSocket::bind(bind_addr).await?;
|
||||
socket.connect(dest).await?;
|
||||
|
||||
let request_id: u64 = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos() as u64;
|
||||
|
||||
let mut buf = [0u8; 12];
|
||||
buf[4..12].copy_from_slice(&request_id.to_be_bytes());
|
||||
socket.send(&buf).await?;
|
||||
|
||||
let mut response = [0u8; 24];
|
||||
let timeout = tokio::time::timeout(Duration::from_secs(2), socket.recv(&mut response)).await;
|
||||
|
||||
match timeout {
|
||||
Ok(Ok(len)) if len >= 24 => {
|
||||
let version_major = response[0] as u32;
|
||||
let version_minor = response[1] as u32;
|
||||
let version_patch = response[2] as u32;
|
||||
let users =
|
||||
u32::from_be_bytes([response[12], response[13], response[14], response[15]]);
|
||||
let max_users =
|
||||
u32::from_be_bytes([response[16], response[17], response[18], response[19]]);
|
||||
let bandwidth =
|
||||
u32::from_be_bytes([response[20], response[21], response[22], response[23]]);
|
||||
|
||||
Ok(ServerStatus {
|
||||
success: true,
|
||||
version: Some((version_major, version_minor, version_patch)),
|
||||
users: Some(users),
|
||||
max_users: Some(max_users),
|
||||
bandwidth: Some(bandwidth),
|
||||
})
|
||||
}
|
||||
Ok(Ok(_)) => bail!("ping response too short"),
|
||||
Ok(Err(e)) => Err(e.into()),
|
||||
Err(_) => bail!("ping timed out"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user