Skip to main content

steel_protocol/
packet_traits.rs

1//! # Steel Protocol Packet Traits
2//!
3//! This module contains the traits for the packets.
4use std::{
5    io::{Cursor, Write},
6    num::NonZeroU32,
7    sync::Arc,
8};
9
10use flate2::{Compression, write::ZlibEncoder};
11use serde::Deserialize;
12use steel_utils::{
13    FrontVec,
14    codec::VarInt,
15    serial::{ReadFrom, WriteTo},
16};
17
18use crate::utils::{ConnectionProtocol, MAX_PACKET_DATA_SIZE, MAX_PACKET_SIZE, PacketError};
19
20// These are the network read/write traits
21/// A trait for packets sent from the server to the client.
22pub trait ServerPacket: ReadFrom {
23    /// Reads a packet from the given data.
24    fn read_packet(data: &mut Cursor<&[u8]>) -> Result<Self, PacketError> {
25        Self::read(data).map_err(PacketError::from)
26    }
27}
28
29/// A trait for packets sent from the client to the server.
30pub trait ClientPacket: WriteTo {
31    /// Writes the packet to the given writer.
32    ///
33    /// # Errors
34    /// - If the packet fails to write.
35    /// - If the protocol is invalid.
36    fn write_packet(
37        &self,
38        writer: &mut impl Write,
39        protocol: ConnectionProtocol,
40    ) -> Result<(), PacketError> {
41        let packet_id = self
42            .get_id(protocol)
43            .ok_or(PacketError::InvalidProtocol(format!(
44                "Invalid protocol {protocol:?}"
45            )))?;
46        VarInt(packet_id).write(writer)?;
47        self.write(writer).map_err(PacketError::from)
48    }
49
50    /// Gets the ID of the packet for the given protocol.
51    fn get_id(&self, protocol: ConnectionProtocol) -> Option<i32>;
52}
53
54/// Information about compression.
55#[derive(Copy, Clone, Debug, Deserialize)]
56pub struct CompressionInfo {
57    /// The compression threshold used when compression is enabled.
58    /// Its an `NonZeroU32` to allow for nullptr optimization in `Option<Self>` cases
59    pub threshold: NonZeroU32,
60    /// A value between `0..9`.
61    /// `1` = Optimize for the best speed of encoding.
62    /// `9` = Optimize for the size of data being encoded.
63    pub level: i32,
64}
65
66impl CompressionInfo {
67    /// Vanilla default compression threshold in bytes.
68    pub const DEFAULT_THRESHOLD: u32 = 256;
69    /// Steel's default zlib compression level.
70    pub const DEFAULT_LEVEL: i32 = 4;
71}
72
73impl Default for CompressionInfo {
74    #[expect(
75        clippy::unwrap_used,
76        reason = "the default threshold is a known nonzero compression threshold"
77    )]
78    fn default() -> Self {
79        Self {
80            threshold: NonZeroU32::new(Self::DEFAULT_THRESHOLD).unwrap(),
81            level: Self::DEFAULT_LEVEL,
82        }
83    }
84}
85
86/// Represents an encoded clientbound packet, optionally applying compression based on threshold and level.
87///
88/// # Packet Size Limits
89/// - Maximum packet size: 2097151 bytes (2^21 - 1, max 3-byte `VarInt`)
90/// - Maximum uncompressed size for compressed packets: 8388608 bytes (2^23)
91/// - Length field must not exceed 3 bytes
92///
93/// # Packet Encoding Format
94///
95/// **Without Compression:**
96/// ```text
97/// [Length: VarInt]     Length of (Packet ID + Data)
98/// [Packet ID: VarInt]  Protocol ID from packet report
99/// [Data: Byte Array]   Packet payload
100/// ```
101///
102/// **With Compression (size >= threshold):**
103/// ```text
104/// [Length: VarInt]     Length of (Data Length + compressed data)
105/// [Data Length: VarInt] Length of uncompressed (Packet ID + Data)
106/// [Compressed Data]    zlib compressed (Packet ID + Data)
107/// ```
108///
109/// **With Compression (size < threshold):**
110/// ```text
111/// [Length: VarInt]     Length of (Data Length + uncompressed data)
112/// [Data Length: VarInt] 0 to indicate uncompressed
113/// [Packet ID: VarInt]  Protocol ID from packet report
114/// [Data: Byte Array]   Uncompressed packet payload
115/// ```
116///
117/// Compression is only applied when:
118/// 1. Compression is enabled via Set Compression packet
119/// 2. The uncompressed data length meets/exceeds the threshold
120/// 3. The threshold is non-negative
121#[derive(Clone)]
122pub struct EncodedPacket {
123    // This is optimized for reduces allocation
124    /// The encoded data.
125    pub encoded_data: Arc<FrontVec>,
126}
127
128impl EncodedPacket {
129    fn from_data_uncompressed(mut packet_data: FrontVec) -> Result<Self, PacketError> {
130        let data_len = packet_data.len();
131        let varint_size = VarInt::written_size(data_len as i32);
132
133        let complete_len = varint_size + data_len;
134        if complete_len > MAX_PACKET_SIZE {
135            return Err(PacketError::TooLong(complete_len));
136        }
137
138        VarInt(data_len as i32).set_in_front(&mut packet_data, varint_size);
139
140        Ok(Self {
141            encoded_data: Arc::new(packet_data),
142        })
143    }
144
145    fn from_packet_data(
146        mut packet_data: FrontVec,
147        compression: CompressionInfo,
148    ) -> Result<Self, PacketError> {
149        let data_len = packet_data.len();
150        // We dont need any more size check to convert to i32 as MAX_PACKET_DATA_SIZE < i32::MAX
151        if data_len + VarInt::MAX_SIZE * 2 > MAX_PACKET_DATA_SIZE {
152            Err(PacketError::TooLong(data_len))?;
153        }
154
155        if data_len >= compression.threshold.get() as _ {
156            let mut buf = FrontVec::new(10);
157            let mut compressor =
158                ZlibEncoder::new(&mut buf, Compression::new(compression.level as u32));
159
160            compressor
161                .write_all(&packet_data)
162                .map_err(|e| PacketError::CompressionFailed(e.to_string()))?;
163            compressor
164                .finish()
165                .map_err(|e| PacketError::CompressionFailed(e.to_string()))?;
166
167            // compressed data cant be larger so we dont need to check the size again
168            let varint_size = VarInt::written_size(data_len as i32);
169            let full_len = varint_size + buf.len();
170            let full_varint_size = VarInt::written_size(full_len as i32);
171
172            VarInt(data_len as i32).set_in_front(&mut buf, varint_size);
173            VarInt(full_len as i32).set_in_front(&mut buf, full_varint_size);
174            log::trace!(
175                "data length: {data_len}, full length: {full_len}, varint size: {varint_size}, full varint size: {full_varint_size}"
176            );
177
178            Ok(Self {
179                encoded_data: Arc::new(buf),
180            })
181        } else {
182            // Pushed before data:
183            // Length of (Data Length) + length of compressed (Packet ID + Data)
184            // 0 to indicate uncompressed
185
186            let data_len_with_header = data_len + 1;
187            let varint_size = VarInt::written_size(data_len_with_header as i32);
188
189            VarInt(0).set_in_front(&mut packet_data, 1);
190            VarInt(data_len_with_header as i32).set_in_front(&mut packet_data, varint_size);
191
192            Ok(Self {
193                encoded_data: Arc::new(packet_data),
194            })
195        }
196    }
197
198    /// Creates a new `EncodedPacket` from a bare packet.
199    ///
200    /// # Errors
201    /// - If the packet fails to write.
202    /// - If the packet fails to compress.
203    pub fn from_bare<P: ClientPacket>(
204        packet: P,
205        compression: Option<CompressionInfo>,
206        protocol: ConnectionProtocol,
207    ) -> Result<Self, PacketError> {
208        let buf = Self::write_vec(packet, protocol)?;
209        Self::from_data(buf, compression)
210    }
211
212    fn write_vec<P: ClientPacket>(
213        packet: P,
214        protocol: ConnectionProtocol,
215    ) -> Result<FrontVec, PacketError> {
216        let mut buf = FrontVec::new(6);
217        packet.write_packet(&mut buf, protocol)?;
218        Ok(buf)
219    }
220
221    fn from_data(buf: FrontVec, compression: Option<CompressionInfo>) -> Result<Self, PacketError> {
222        if let Some(compression) = compression {
223            Self::from_packet_data(buf, compression)
224        } else {
225            Self::from_data_uncompressed(buf)
226        }
227    }
228}