Skip to main content

rust_mc_status/protocol/
bedrock.rs

1//! Bedrock Edition ping protocol (RakNet Unconnected Ping/Pong).
2//!
3//! Uses the RakNet offline message protocol over UDP:
4//!
5//! ```text
6//! Client → Server: Unconnected Ping (0x01)
7//!   - 8-byte timestamp
8//!   - Magic bytes (offline message ID)
9//!   - 8-byte client GUID
10//!
11//! Server → Client: Unconnected Pong (0x1C)
12//!   - 8-byte timestamp echo
13//!   - 8-byte server GUID
14//!   - Magic bytes
15//!   - MOTD string (semicolon-delimited, see BedrockStatus)
16//! ```
17//!
18//! The MOTD string is parsed by [`BedrockStatus::parse`](crate::BedrockStatus).
19//!
20//! # UDP and proxies
21//!
22//! UDP cannot be proxied through standard SOCKS5 or HTTP CONNECT.
23//! A proxy configured without [`UdpSupport::Yes`](crate::proxy::UdpSupport)
24//! causes an immediate [`ProxyError::UdpUnsupported`](crate::error::ProxyError)
25//! before any network connection is made.
26//!
27//! # Unit struct design
28//!
29//! [`BedrockProtocol`] is a zero-size unit struct — `Copy`, `Clone`, and `Default`.
30
31use std::time::{Duration, SystemTime};
32
33use tokio::net::UdpSocket;
34use tokio::time::timeout;
35
36use crate::error::McError;
37use crate::models::{BedrockStatus, PingResult, ServerData};
38use crate::proxy::ProxyConfig;
39#[cfg(feature = "proxy")]
40use crate::proxy::UdpSupport;
41use super::{PingProtocol, ResolvedTarget};
42use crate::core::time::{start_timer, elapsed_ms};
43
44const READ_BUFFER_SIZE:          usize = 4096;
45const BEDROCK_PING_PACKET_SIZE:  usize = 35;
46const BEDROCK_MIN_RESPONSE_SIZE: usize = 35;
47
48// ─── Protocol impl ────────────────────────────────────────────────────────────
49
50/// Bedrock Edition ping over UDP (RakNet Unconnected Ping/Pong).
51///
52/// Unit struct — zero-size, `Copy`, no heap allocation.
53#[derive(Debug, Clone, Copy, Default)]
54pub struct BedrockProtocol;
55
56impl PingProtocol for BedrockProtocol {
57    fn name(&self) -> &'static str { "bedrock" }
58
59    async fn ping(
60        &self,
61        target:  &ResolvedTarget,
62        tout:    Duration,
63        proxy:   Option<&ProxyConfig>,
64    ) -> Result<PingResult, McError> {
65        // Reject proxy configs that don't advertise UDP support.
66        #[cfg(feature = "proxy")]
67        if let Some(cfg) = proxy
68            && cfg.udp_support() != UdpSupport::Yes {
69                return Err(McError::proxy_udp_unsupported(cfg.addr().to_string()));
70            }
71        let _ = proxy; // silence unused warning when feature is disabled
72
73        let start  = start_timer();
74        let socket = UdpSocket::bind("0.0.0.0:0").await.map_err(McError::Io)?;
75
76        timeout(tout, socket.send_to(&build_ping(), target.addr))
77            .await
78            .map_err(|_| McError::timeout())?
79            .map_err(McError::Io)?;
80
81        let mut buf = [0u8; READ_BUFFER_SIZE];
82        let (len, _) = timeout(tout, socket.recv_from(&mut buf))
83            .await
84            .map_err(|_| McError::timeout())?
85            .map_err(McError::Io)?;
86
87        if len < BEDROCK_MIN_RESPONSE_SIZE {
88            return Err(McError::invalid_response("response too short"));
89        }
90
91        let latency = elapsed_ms(start);
92        let pong    = String::from_utf8(buf[BEDROCK_PING_PACKET_SIZE..len].to_vec())
93            .unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
94
95        Ok(PingResult::new(ServerData::Bedrock(BedrockStatus::parse(&pong)?), latency))
96    }
97}
98
99// ─── Packet builder ───────────────────────────────────────────────────────────
100
101fn build_ping() -> Vec<u8> {
102    let mut p = Vec::with_capacity(BEDROCK_PING_PACKET_SIZE);
103    p.push(0x01);
104    let ts = SystemTime::now()
105        .duration_since(SystemTime::UNIX_EPOCH)
106        .unwrap_or_default()
107        .as_millis() as u64;
108    p.extend_from_slice(&ts.to_be_bytes());
109    p.extend_from_slice(&[
110        0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
111        0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78,
112    ]);
113    p.extend_from_slice(&[0u8; 8]);
114    p
115}
116
117// ─── BedrockStatus parser ─────────────────────────────────────────────────────