Skip to main content

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
54impl Frame {
55    pub const PROTOCOL_VERSION: i32 = 767;
56    pub const HANDSHAKE_ID: i32 = 0x00;
57    pub const STATUS_REQUEST_ID: i32 = 0x00;
58    pub const STATUS_RESPONSE_ID: i32 = 0x00;
59    pub const PING_REQUEST_ID: i32 = 0x01;
60    pub const PING_RESPONSE_ID: i32 = 0x01;
61
62    /// Checks if an entire message can be decoded from `buf`, advancing the cursor past the header
63    pub fn check(buf: &mut Cursor<&[u8]>) -> Result<(), FrameError> {
64        let available_data = buf.get_ref().len();
65
66        // the varint at the beginning contains the size of the rest of the frame
67        let remaining_data_len: usize =
68            i32::from(buf.read_var_int().ok().context(IncompleteSnafu)?)
69                .try_into()
70                .ok()
71                .context(InvalidLengthSnafu)?;
72        let header_len = buf.position() as usize;
73        let total_len = header_len + remaining_data_len;
74
75        // if we don't have enough data the frame isn't valid yet
76        let is_valid = available_data >= total_len;
77
78        if is_valid {
79            trace!("Valid frame, packet size: {total_len}, header size: {header_len}, body size: {remaining_data_len}, downloaded: {available_data}");
80            Ok(())
81        } else {
82            trace!("Invalid frame, packet size: {total_len}, downloaded: {available_data}");
83            IncompleteSnafu.fail()
84        }
85    }
86
87    /// Parse the body of a frame, after the message has already been validated with `check`.
88    pub fn parse(cursor: &mut Cursor<&[u8]>) -> Result<Frame, FrameError> {
89        let id = i32::from(cursor.read_var_int()?);
90
91        match id {
92            Self::STATUS_RESPONSE_ID => {
93                let json = decode_mc_string(cursor)?;
94                Ok(Frame::StatusResponse { json })
95            }
96            Self::PING_RESPONSE_ID => {
97                // ping response contains the same Java long as the request
98                let payload = cursor.get_i64();
99                Ok(Frame::PingResponse { payload })
100            }
101            _ => InvalidFrameIdSnafu { id }.fail(),
102        }
103    }
104}