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:
Generated
+2
-1
@@ -4275,7 +4275,9 @@ dependencies = [
|
|||||||
name = "mumble-web2-common"
|
name = "mumble-web2-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"color-eyre",
|
||||||
"serde",
|
"serde",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4298,7 +4300,6 @@ dependencies = [
|
|||||||
"hmac-sha256",
|
"hmac-sha256",
|
||||||
"mumble-web2-common",
|
"mumble-web2-common",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand 0.9.2",
|
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
"salvo",
|
"salvo",
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ desktop = [
|
|||||||
"cpal",
|
"cpal",
|
||||||
"dasp_ring_buffer",
|
"dasp_ring_buffer",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
|
"mumble-web2-common/networking",
|
||||||
]
|
]
|
||||||
mobile = [
|
mobile = [
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -141,4 +142,5 @@ mobile = [
|
|||||||
"opus",
|
"opus",
|
||||||
"cpal",
|
"cpal",
|
||||||
"dasp_ring_buffer",
|
"dasp_ring_buffer",
|
||||||
|
"mumble-web2-common/networking",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::app::{Command, SharedState};
|
use crate::app::{Command, SharedState};
|
||||||
use color_eyre::eyre::{bail, Error};
|
use color_eyre::eyre::Error;
|
||||||
use futures_channel::mpsc::UnboundedReceiver;
|
use futures_channel::mpsc::UnboundedReceiver;
|
||||||
use mumble_protocol::control::ClientControlCodec;
|
use mumble_protocol::control::ClientControlCodec;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
@@ -14,7 +14,7 @@ use tokio_rustls::TlsConnector;
|
|||||||
use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _};
|
use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _};
|
||||||
use tracing::{info, instrument};
|
use tracing::{info, instrument};
|
||||||
|
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::ProxyOverrides;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct NoCertificateVerification;
|
struct NoCertificateVerification;
|
||||||
@@ -108,10 +108,6 @@ pub async fn network_connect(
|
|||||||
crate::network_loop(username, state, event_rx, outgoing_send, reader).await
|
crate::network_loop(username, state, event_rx, outgoing_send, reader).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
|
||||||
bail!("status not supported on desktop yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub use tokio::spawn;
|
pub use tokio::spawn;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ impl super::PlatformInterface for DesktopPlatform {
|
|||||||
super::connect::network_connect(address, username, event_rx, overrides, state).await
|
super::connect::network_connect(address, username, event_rx, overrides, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
async fn get_status(
|
||||||
super::connect::get_status(client).await
|
_client: &reqwest::Client,
|
||||||
|
address: &str,
|
||||||
|
) -> color_eyre::Result<ServerStatus> {
|
||||||
|
mumble_web2_common::ping_server(address, 64738).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logging() {
|
fn init_logging() {
|
||||||
|
|||||||
@@ -29,8 +29,11 @@ impl super::PlatformInterface for MobilePlatform {
|
|||||||
super::connect::network_connect(address, username, event_rx, overrides, state).await
|
super::connect::network_connect(address, username, event_rx, overrides, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
async fn get_status(
|
||||||
super::connect::get_status(client).await
|
_client: &reqwest::Client,
|
||||||
|
address: &str,
|
||||||
|
) -> color_eyre::Result<ServerStatus> {
|
||||||
|
mumble_web2_common::ping_server(address, 64738).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logging() {
|
fn init_logging() {
|
||||||
|
|||||||
@@ -86,9 +86,14 @@ pub trait PlatformInterface {
|
|||||||
state: SharedState,
|
state: SharedState,
|
||||||
) -> impl Future<Output = Result<(), Error>>;
|
) -> impl Future<Output = Result<(), Error>>;
|
||||||
|
|
||||||
/// Get server status (user count, version, etc.).
|
/// Get server status (user count, version, etc.) for the given address.
|
||||||
|
///
|
||||||
|
/// On web, this goes through the proxy's /status endpoint and ignores `address`
|
||||||
|
/// (the proxy is bound to a specific server). On desktop/mobile, this pings the
|
||||||
|
/// given address directly via UDP.
|
||||||
fn get_status(
|
fn get_status(
|
||||||
client: &reqwest::Client,
|
client: &reqwest::Client,
|
||||||
|
address: &str,
|
||||||
) -> impl Future<Output = color_eyre::Result<ServerStatus>>;
|
) -> impl Future<Output = color_eyre::Result<ServerStatus>>;
|
||||||
|
|
||||||
/// Load the proxy overrides (proxy URL, cert hash, etc.).
|
/// Load the proxy overrides (proxy URL, cert hash, etc.).
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ impl super::PlatformInterface for StubPlatform {
|
|||||||
|
|
||||||
fn get_status(
|
fn get_status(
|
||||||
_client: &reqwest::Client,
|
_client: &reqwest::Client,
|
||||||
|
_address: &str,
|
||||||
) -> impl Future<Output = color_eyre::Result<ServerStatus>> {
|
) -> impl Future<Output = color_eyre::Result<ServerStatus>> {
|
||||||
async { panic!("stubbed platform") }
|
async { panic!("stubbed platform") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,10 @@ impl super::PlatformInterface for WebPlatform {
|
|||||||
network_connect(address, username, event_rx, overrides, state).await
|
network_connect(address, username, event_rx, overrides, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
async fn get_status(
|
||||||
|
client: &reqwest::Client,
|
||||||
|
_address: &str,
|
||||||
|
) -> color_eyre::Result<ServerStatus> {
|
||||||
Ok(client
|
Ok(client
|
||||||
.get(absolute_url("status")?)
|
.get(absolute_url("status")?)
|
||||||
.send()
|
.send()
|
||||||
|
|||||||
@@ -3,5 +3,10 @@ name = "mumble-web2-common"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
networking = ["dep:tokio", "dep:color-eyre"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true }
|
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 max_users: Option<u32>,
|
||||||
pub bandwidth: 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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+14
-10
@@ -502,15 +502,6 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
let user_config = use_context::<ConfigSystem>();
|
let user_config = use_context::<ConfigSystem>();
|
||||||
let net: Coroutine<Command> = use_coroutine_handle();
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
|
|
||||||
let last_status = use_signal(|| None::<color_eyre::Result<ServerStatus>>);
|
|
||||||
use_resource(move || async move {
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
loop {
|
|
||||||
*last_status.write_unchecked() = Some(Platform::get_status(&client).await);
|
|
||||||
Platform::sleep(std::time::Duration::from_secs_f32(1.0)).await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut address_input = use_signal(|| user_config.config_get::<String>("server_url"));
|
let mut address_input = use_signal(|| user_config.config_get::<String>("server_url"));
|
||||||
let address = use_memo(move || {
|
let address = use_memo(move || {
|
||||||
if let Some(addr) = address_input() {
|
if let Some(addr) = address_input() {
|
||||||
@@ -522,6 +513,19 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let last_status = use_signal(|| None::<color_eyre::Result<ServerStatus>>);
|
||||||
|
use_resource(move || {
|
||||||
|
let addr = address();
|
||||||
|
async move {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
loop {
|
||||||
|
*last_status.write_unchecked() =
|
||||||
|
Some(Platform::get_status(&client, &addr).await);
|
||||||
|
Platform::sleep(std::time::Duration::from_secs_f32(1.0)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut username = use_signal(|| {
|
let mut username = use_signal(|| {
|
||||||
user_config
|
user_config
|
||||||
.config_get::<String>("username")
|
.config_get::<String>("username")
|
||||||
@@ -640,7 +644,7 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
Some(Err(_)) => rsx!(div {
|
Some(Err(_)) => rsx!(div {
|
||||||
class: "login_status is_error",
|
class: "login_status is_error",
|
||||||
span {
|
span {
|
||||||
"Could not reach proxy server"
|
"Could not reach server"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -12,7 +12,7 @@ tokio-rustls = "0.26"
|
|||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
tracing = { version = "^0.1.40", features = ["async-await"] }
|
tracing = { version = "^0.1.40", features = ["async-await"] }
|
||||||
tracing-subscriber = { version = "^0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "^0.3.18", features = ["env-filter"] }
|
||||||
mumble-web2-common = { workspace = true }
|
mumble-web2-common = { workspace = true, features = ["networking"] }
|
||||||
salvo = { version = "^0.84.2", features = [
|
salvo = { version = "^0.84.2", features = [
|
||||||
"quinn",
|
"quinn",
|
||||||
"eyre",
|
"eyre",
|
||||||
@@ -28,4 +28,3 @@ rcgen = "^0.13.2"
|
|||||||
hmac-sha256 = "^1.1.8"
|
hmac-sha256 = "^1.1.8"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
url = { version = "2", features = ["serde"] }
|
url = { version = "2", features = ["serde"] }
|
||||||
rand = "0.9.2"
|
|
||||||
|
|||||||
+6
-65
@@ -1,6 +1,5 @@
|
|||||||
use color_eyre::eyre::{anyhow, bail, Context, Result};
|
use color_eyre::eyre::{anyhow, bail, Context, Result};
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::{ping_server, ProxyOverrides, ServerStatus};
|
||||||
use rand::Rng;
|
|
||||||
use salvo::conn::rustls::{Keycert, RustlsConfig};
|
use salvo::conn::rustls::{Keycert, RustlsConfig};
|
||||||
use salvo::cors::{AllowOrigin, Cors};
|
use salvo::cors::{AllowOrigin, Cors};
|
||||||
use salvo::logging::Logger;
|
use salvo::logging::Logger;
|
||||||
@@ -26,8 +25,6 @@ use tracing_subscriber::filter::LevelFilter;
|
|||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod ping;
|
|
||||||
|
|
||||||
fn default_cert_alt_names() -> Vec<String> {
|
fn default_cert_alt_names() -> Vec<String> {
|
||||||
vec!["localhost".into()]
|
vec!["localhost".into()]
|
||||||
}
|
}
|
||||||
@@ -179,70 +176,14 @@ pub struct StatusCraft {
|
|||||||
impl StatusCraft {
|
impl StatusCraft {
|
||||||
#[craft(handler)]
|
#[craft(handler)]
|
||||||
async fn get_status(&self) -> Json<ServerStatus> {
|
async fn get_status(&self) -> Json<ServerStatus> {
|
||||||
let mut server_status = ServerStatus::default();
|
let addr = self.mumble_server_address;
|
||||||
|
match ping_server(&addr.ip().to_string(), addr.port()).await {
|
||||||
let ping_packet = ping::PingPacket {
|
Ok(status) => Json(status),
|
||||||
id: rand::rng().random(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let sock = match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not bind udp socket: {}", e);
|
error!("ping failed: {e:#}");
|
||||||
return Json(server_status);
|
Json(ServerStatus::default())
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match sock.connect(self.mumble_server_address).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not send ping packet: {}", e);
|
|
||||||
return Json(server_status);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match sock.send(&<[u8; 12]>::from(ping_packet)).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not send ping packet");
|
|
||||||
return Json(server_status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pong_buf: [u8; 24] = [0; 24];
|
|
||||||
|
|
||||||
match tokio::time::timeout(
|
|
||||||
tokio::time::Duration::from_secs(1),
|
|
||||||
sock.recv(&mut pong_buf),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not send ping packet");
|
|
||||||
return Json(server_status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pong_packet = match ping::PongPacket::try_from(pong_buf.as_slice()) {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Could not parse pong packet: {:?}", e);
|
|
||||||
return Json(server_status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
server_status.success = true;
|
|
||||||
server_status.version = Some((
|
|
||||||
pong_packet.version & 0xFF,
|
|
||||||
(pong_packet.version >> 8) & 0xFF,
|
|
||||||
(pong_packet.version >> 16) & 0xFF,
|
|
||||||
));
|
|
||||||
server_status.users = Some(pong_packet.users);
|
|
||||||
server_status.max_users = Some(pong_packet.max_users);
|
|
||||||
server_status.bandwidth = Some(pong_packet.bandwidth);
|
|
||||||
|
|
||||||
Json(server_status)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
// This code was taken from mumble-protocol-2x (https://github.com/dblsaiko/rust-mumble-protocol)
|
|
||||||
// and originally from mumble-protocol (https://github.com/Johni0702/rust-mumble-protocol)
|
|
||||||
// These projects are licensed under MIT and Apache 2.0.
|
|
||||||
|
|
||||||
//! Ping messages and codec
|
|
||||||
//!
|
|
||||||
//! A Mumble client can send periodic UDP [PingPacket]s to servers
|
|
||||||
//! in order to query their current state and measure latency.
|
|
||||||
//! A server will usually respond with a corresponding [PongPacket] containing
|
|
||||||
//! the requested details.
|
|
||||||
//!
|
|
||||||
//! Both packets are of fixed size and can be converted to/from `u8` arrays/slices via
|
|
||||||
//! the respective `From`/`TryFrom` impls.
|
|
||||||
|
|
||||||
/// A ping packet sent to the server.
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct PingPacket {
|
|
||||||
/// Opaque, client-generated id.
|
|
||||||
///
|
|
||||||
/// Will be returned by the server unmodified and can be used to correlate
|
|
||||||
/// pong replies to ping requests to e.g. calculate latency.
|
|
||||||
pub id: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pong packet sent to the client in reply to a previously received [PingPacket].
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct PongPacket {
|
|
||||||
/// Opaque, client-generated id.
|
|
||||||
///
|
|
||||||
/// Should match the value in the corresponding [PingPacket].
|
|
||||||
pub id: u64,
|
|
||||||
|
|
||||||
/// Server version. E.g. `0x010300` for `1.3.0`.
|
|
||||||
pub version: u32,
|
|
||||||
|
|
||||||
/// Current amount of users connected to the server.
|
|
||||||
pub users: u32,
|
|
||||||
|
|
||||||
/// Configured limit on the amount of users which can be connected to the server.
|
|
||||||
pub max_users: u32,
|
|
||||||
|
|
||||||
/// Maximum bandwidth for server-bound speech per client in bits per second
|
|
||||||
pub bandwidth: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error during parsing of a [PingPacket].
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ParsePingError {
|
|
||||||
/// Ping packets must always be 12 bytes in size.
|
|
||||||
InvalidSize,
|
|
||||||
/// Ping packets must have an all zero header of 4 bytes.
|
|
||||||
InvalidHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for PingPacket {
|
|
||||||
type Error = ParsePingError;
|
|
||||||
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
match <[u8; 12]>::try_from(buf) {
|
|
||||||
Ok(array) => {
|
|
||||||
if array[0..4] != [0, 0, 0, 0] {
|
|
||||||
Err(ParsePingError::InvalidHeader)
|
|
||||||
} else {
|
|
||||||
Ok(Self {
|
|
||||||
id: u64::from_be_bytes(array[4..12].try_into().unwrap()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => Err(ParsePingError::InvalidSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PingPacket> for [u8; 12] {
|
|
||||||
fn from(packet: PingPacket) -> Self {
|
|
||||||
let id = packet.id.to_be_bytes();
|
|
||||||
// Is there no nicer way to do this?
|
|
||||||
[
|
|
||||||
0, 0, 0, 0, id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error during parsing of a [PongPacket].
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ParsePongError {
|
|
||||||
/// Pong packets must always be 24 bytes in size.
|
|
||||||
InvalidSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for PongPacket {
|
|
||||||
type Error = ParsePongError;
|
|
||||||
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
match <[u8; 24]>::try_from(buf) {
|
|
||||||
Ok(array) => Ok(Self {
|
|
||||||
version: u32::from_be_bytes(array[0..4].try_into().unwrap()),
|
|
||||||
id: u64::from_be_bytes(array[4..12].try_into().unwrap()),
|
|
||||||
users: u32::from_be_bytes(array[12..16].try_into().unwrap()),
|
|
||||||
max_users: u32::from_be_bytes(array[16..20].try_into().unwrap()),
|
|
||||||
bandwidth: u32::from_be_bytes(array[20..24].try_into().unwrap()),
|
|
||||||
}),
|
|
||||||
Err(_) => Err(ParsePongError::InvalidSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PongPacket> for [u8; 24] {
|
|
||||||
fn from(packet: PongPacket) -> Self {
|
|
||||||
let version = packet.version.to_be_bytes();
|
|
||||||
let id = packet.id.to_be_bytes();
|
|
||||||
let users = packet.users.to_be_bytes();
|
|
||||||
let max_users = packet.max_users.to_be_bytes();
|
|
||||||
let bandwidth = packet.bandwidth.to_be_bytes();
|
|
||||||
// Is there no nicer way to do this?
|
|
||||||
[
|
|
||||||
version[0],
|
|
||||||
version[1],
|
|
||||||
version[2],
|
|
||||||
version[3],
|
|
||||||
id[0],
|
|
||||||
id[1],
|
|
||||||
id[2],
|
|
||||||
id[3],
|
|
||||||
id[4],
|
|
||||||
id[5],
|
|
||||||
id[6],
|
|
||||||
id[7],
|
|
||||||
users[0],
|
|
||||||
users[1],
|
|
||||||
users[2],
|
|
||||||
users[3],
|
|
||||||
max_users[0],
|
|
||||||
max_users[1],
|
|
||||||
max_users[2],
|
|
||||||
max_users[3],
|
|
||||||
bandwidth[0],
|
|
||||||
bandwidth[1],
|
|
||||||
bandwidth[2],
|
|
||||||
bandwidth[3],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user