elytra_ping/protocol/
frame.rs

1use std::io::Cursor;
2
3use bytes::Buf;
4use mc_varint::{VarInt, VarIntRead};
5use snafu::{Backtrace, OptionExt, Snafu};
6use tracing::trace;
7
8use crate::mc_string::{decode_mc_string, McStringError};
9
10#[derive(Snafu, Debug)]
11pub enum FrameError {
12    /// Received an incomplete frame.
13    Incomplete { backtrace: Backtrace },
14    /// I/O error.
15    #[snafu(display("I/O error: {source}"), context(false))]
16    Io {
17        source: std::io::Error,
18        backtrace: Backtrace,
19    },
20    /// Received a frame with an invalid length.
21    InvalidLength { backtrace: Backtrace },
22    /// Received a frame with an invalid id.
23    InvalidFrameId { id: i32, backtrace: Backtrace },
24    /// Failed to decode string.
25    #[snafu(display("Failed to decode string: {source}"), context(false))]
26    StringDecodeFailed {
27        #[snafu(backtrace)]
28        source: McStringError,
29    },
30}
31
32#[derive(Debug)]
33#[non_exhaustive]
34pub enum Frame {
35    Handshake {
36        protocol: VarInt,
37        address: String,
38        port: u16,
39        // should be 1 for status
40        state: VarInt,
41    },
42    StatusRequest,
43    StatusResponse {
44        json: String,
45    },
46    PingRequest {
47        payload: i64,
48    },
49    PingResponse {
50        payload: i64,
51    },
52}
53
54/// Controls what packets a server can receive
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56#[deprecated(
57    since = "5.1.0",
58    note = "Elytra Ping as a SLP server is untested and not supported"
59)]
60pub enum ServerState {
61    /// Waiting for the Handshake packet
62    Handshake,
63    /// Ready to respond to status and ping requests
64    Status,
65}
66
67impl Frame {
68    pub const PROTOCOL_VERSION: i32 = 767;
69    pub const HANDSHAKE_ID: i32 = 0x00;
70    pub const STATUS_REQUEST_ID: i32 = 0x00;
71    pub const STATUS_RESPONSE_ID: i32 = 0x00;
72    pub const PING_REQUEST_ID: i32 = 0x01;
73    pub const PING_RESPONSE_ID: i32 = 0x01;
74
75    /// Checks if an entire message can be decoded from `buf`, advancing the cursor past the header
76    pub fn check(buf: &mut Cursor<&[u8]>) -> Result<(), FrameError> {
77        let available_data = buf.get_ref().len();
78
79        // the varint at the beginning contains the size of the rest of the frame
80        let remaining_data_len: usize =
81            i32::from(buf.read_var_int().ok().context(IncompleteSnafu)?)
82                .try_into()
83                .ok()
84                .context(InvalidLengthSnafu)?;
85        let header_len = buf.position() as usize;
86        let total_len = header_len + remaining_data_len;
87
88        // if we don't have enough data the frame isn't valid yet
89        let is_valid = available_data >= total_len;
90
91        if is_valid {
92            trace!("Valid frame, packet size: {total_len}, header size: {header_len}, body size: {remaining_data_len}, downloaded: {available_data}");
93            Ok(())
94        } else {
95            trace!("Invalid frame, packet size: {total_len}, downloaded: {available_data}");
96            IncompleteSnafu.fail()
97        }
98    }
99
100    /// Parse the body of a frame, after the message has already been validated with `check`.
101    ///
102    /// # Arguments
103    ///
104    /// * `src` - The buffer containing the message
105    /// * `server_state` - Switches between which type of frame to accept. Set to None to accept frames for the client.
106    pub fn parse(
107        cursor: &mut Cursor<&[u8]>,
108        server_state: Option<ServerState>,
109    ) -> Result<Frame, FrameError> {
110        let id = i32::from(cursor.read_var_int()?);
111
112        match server_state {
113            Some(ServerState::Handshake) => {
114                if id == Self::HANDSHAKE_ID {
115                    let protocol = cursor.read_var_int()?;
116                    let address = decode_mc_string(cursor)?;
117                    let port = cursor.get_u16();
118                    let state = cursor.read_var_int()?;
119                    return Ok(Frame::Handshake {
120                        protocol,
121                        address,
122                        port,
123                        state,
124                    });
125                }
126            }
127            Some(ServerState::Status) => {
128                match id {
129                    Self::STATUS_REQUEST_ID => {
130                        return Ok(Frame::StatusRequest);
131                    }
132                    Self::PING_REQUEST_ID => {
133                        // ping request a contains (usually) meaningless Java long
134                        let payload = cursor.get_i64();
135                        return Ok(Frame::PingRequest { payload });
136                    }
137                    _ => {}
138                }
139            }
140            None => {
141                match id {
142                    Self::STATUS_RESPONSE_ID => {
143                        let json = decode_mc_string(cursor)?;
144                        return Ok(Frame::StatusResponse { json });
145                    }
146                    Self::PING_RESPONSE_ID => {
147                        // ping response contains the same Java long as the request
148                        let payload = cursor.get_i64();
149                        return Ok(Frame::PingResponse { payload });
150                    }
151                    _ => {}
152                }
153            }
154        }
155
156        InvalidFrameIdSnafu { id }.fail()
157    }
158}