mc_query/rcon/
packet.rs

1use crate::errors::RconProtocolError;
2use bytes::{Buf, BufMut, Bytes, BytesMut};
3use std::mem::size_of;
4
5use super::{MAX_LEN_CLIENTBOUND, MAX_LEN_SERVERBOUND};
6
7#[derive(Debug)]
8pub(super) enum RconPacketType {
9    Response,
10    Login,
11    RunCommand,
12}
13
14impl From<RconPacketType> for i32 {
15    fn from(packet_type: RconPacketType) -> Self {
16        match packet_type {
17            RconPacketType::Response => 0,
18            RconPacketType::RunCommand => 2,
19            RconPacketType::Login => 3,
20        }
21    }
22}
23
24impl TryFrom<i32> for RconPacketType {
25    type Error = RconProtocolError;
26
27    fn try_from(value: i32) -> Result<Self, Self::Error> {
28        match value {
29            0 => Ok(RconPacketType::Response),
30            2 => Ok(RconPacketType::RunCommand),
31            3 => Ok(RconPacketType::Login),
32            _ => Err(RconProtocolError::InvalidPacketType),
33        }
34    }
35}
36
37#[derive(Debug)]
38pub(super) struct RconPacket {
39    pub request_id: i32,
40    pub packet_type: RconPacketType,
41    pub payload: String,
42}
43
44impl RconPacket {
45    pub fn new(
46        request_id: i32,
47        packet_type: RconPacketType,
48        payload: String,
49    ) -> Result<Self, RconProtocolError> {
50        if !payload.is_ascii() {
51            return Err(RconProtocolError::NonAsciiPayload);
52        }
53
54        if payload.len() > Ord::max(MAX_LEN_CLIENTBOUND, MAX_LEN_SERVERBOUND) {
55            return Err(RconProtocolError::PayloadTooLong);
56        }
57
58        Ok(Self {
59            request_id,
60            packet_type,
61            payload,
62        })
63    }
64
65    pub fn bytes(self) -> Bytes {
66        Bytes::from(self)
67    }
68}
69
70impl TryFrom<Bytes> for RconPacket {
71    type Error = RconProtocolError;
72
73    fn try_from(mut bytes: Bytes) -> Result<Self, Self::Error> {
74        let len = bytes.get_i32_le(); // length of remaining packet (not including this integer)
75        let request_id = bytes.get_i32_le();
76        let packet_type = bytes.get_i32_le();
77
78        let mut payload = String::new();
79        loop {
80            let current = bytes.get_u8();
81            if current == 0 {
82                // null terminated ASCII string, so stop reading here
83                break;
84            }
85
86            payload.push(current as char);
87        }
88
89        // if the payload is already normal ASCII (without 0xa7), no need to
90        // check each character to be ASCII or 0xa7
91        if !payload.is_ascii() {
92            for c in payload.chars() {
93                // 0xa7 is an acceptable (though non-ASCII) character
94                if !c.is_ascii() && (c as u8) != 0xa7 {
95                    return Err(RconProtocolError::NonAsciiPayload);
96                }
97            }
98        }
99
100        let pad = bytes.get_u8(); // there must be a remaining 0 byte as padding
101        if pad != 0 {
102            return Err(RconProtocolError::InvalidRconResponse);
103        }
104
105        // validate if the lengths match
106        if get_remaining_length(&payload) != len {
107            return Err(RconProtocolError::InvalidRconResponse);
108        }
109
110        Self::new(request_id, packet_type.try_into()?, payload)
111    }
112}
113
114impl From<RconPacket> for Bytes {
115    fn from(packet: RconPacket) -> Self {
116        let len = get_remaining_length(&packet.payload);
117        let packet_type: i32 = packet.packet_type.into();
118
119        let mut bytes = BytesMut::new();
120
121        bytes.put_i32_le(len);
122        bytes.put_i32_le(packet.request_id);
123        bytes.put_i32_le(packet_type);
124        bytes.put(packet.payload.as_bytes());
125        bytes.put_u16(0x00_00);
126
127        bytes.freeze()
128    }
129}
130
131/// Get the *remaining length* of the packet given its payload.
132///
133/// Remaining length here refers to the length of the packet in bytes excluding
134/// the first four bytes which communicate this value. So it refers to the
135/// length of the packet *after* the length field.
136///
137/// As the remainder of the packet is composed of two [i32]s (request ID and type),
138/// the payload, and **TWO** 0 bytes (because rust strings are not null-terminated),
139/// it is the size of two [i32]s + the length of the payload + 2.
140fn get_remaining_length(payload: &str) -> i32 {
141    (payload.len() + size_of::<i32>() * 2 + 2) as i32
142}