Skip to main content

steel_protocol/
packet_reader.rs

1//! # Steel Protocol Packet Reader
2//!
3//! This module contains the implementation of the packet reader.
4/*
5Credit to https://github.com/Pumpkin-MC/Pumpkin/ for this implementation.
6*/
7
8use std::{
9    io::{self, Read},
10    num::NonZeroU32,
11    pin::Pin,
12    task::{Context, Poll},
13};
14
15use aes::cipher::KeyIvInit;
16use flate2::read::ZlibDecoder;
17use steel_utils::codec::VarInt;
18use steel_utils::serial::ReadFrom;
19use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf};
20
21use crate::utils::{
22    Aes128Cfb8Dec, MAX_PACKET_DATA_SIZE, MAX_PACKET_SIZE, PacketError, RawPacket, StreamDecryptor,
23};
24
25/// A reader that can decrypt data.
26pub enum DecryptionReader<R: AsyncRead + Unpin> {
27    /// A reader that decrypts data.
28    Decrypt(Box<StreamDecryptor<R>>),
29    /// A reader that does not decrypt data.
30    None(R),
31}
32
33impl<R: AsyncRead + Unpin> DecryptionReader<R> {
34    /// Upgrades the reader to decrypt data.
35    ///
36    /// # Panics
37    /// - If the reader is already decrypting data.
38    #[must_use]
39    pub fn upgrade(self, cipher: Aes128Cfb8Dec) -> Self {
40        match self {
41            Self::None(stream) => Self::Decrypt(Box::new(StreamDecryptor::new(cipher, stream))),
42            Self::Decrypt(_) => panic!("Cannot upgrade a stream that already has a cipher!"),
43        }
44    }
45}
46
47impl<R: AsyncRead + Unpin> AsyncRead for DecryptionReader<R> {
48    #[inline]
49    fn poll_read(
50        self: Pin<&mut Self>,
51        cx: &mut Context<'_>,
52        buf: &mut ReadBuf<'_>,
53    ) -> Poll<io::Result<()>> {
54        match self.get_mut() {
55            Self::Decrypt(reader) => {
56                let reader = Pin::new(reader);
57                reader.poll_read(cx, buf)
58            }
59            Self::None(reader) => {
60                let reader = Pin::new(reader);
61                reader.poll_read(cx, buf)
62            }
63        }
64    }
65}
66
67/// Decoder: Client -> Server
68/// Supports `ZLib` decoding/decompression
69/// Supports Aes128 Encryption
70pub struct TCPNetworkDecoder<R: AsyncRead + Unpin> {
71    reader: DecryptionReader<R>,
72    compression: Option<NonZeroU32>,
73}
74
75impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> {
76    /// Creates a new `TCPNetworkDecoder`.
77    pub const fn new(reader: R) -> Self {
78        Self {
79            reader: DecryptionReader::None(reader),
80            compression: None,
81        }
82    }
83
84    /// Sets the compression threshold for the decoder.
85    pub const fn set_compression(&mut self, threshold: NonZeroU32) {
86        self.compression = Some(threshold);
87    }
88
89    /// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
90    ///
91    /// # Panics
92    /// - If the reader is already decrypting data.
93    /// - If the key is invalid.
94    pub fn set_encryption(&mut self, key: &[u8; 16]) {
95        if matches!(self.reader, DecryptionReader::Decrypt(_)) {
96            panic!("Cannot upgrade a stream that already has a cipher!");
97        }
98        let cipher = Aes128Cfb8Dec::new_from_slices(key, key).expect("invalid key");
99        replace_with::replace_with_or_abort(&mut self.reader, |decoder| decoder.upgrade(cipher));
100    }
101
102    /// Gets a raw packet from the stream.
103    ///
104    /// # Errors
105    /// - If the packet length is invalid.
106    /// - If the packet is too long.
107    /// - If the packet is not compressed when it should be.
108    /// - If the packet fails to decompress.
109    #[expect(clippy::cast_sign_loss)]
110    pub async fn get_raw_packet(&mut self) -> Result<RawPacket, PacketError> {
111        let packet_len = VarInt::read_async(&mut self.reader).await? as usize;
112
113        if packet_len > MAX_PACKET_SIZE {
114            Err(PacketError::OutOfBounds)?;
115        }
116
117        // Read the entire packet data into a buffer
118        let mut packet_data = vec![0u8; packet_len];
119        self.reader
120            .read_exact(&mut packet_data)
121            .await
122            .map_err(|e| PacketError::Other(e.to_string()))?;
123
124        let mut cursor = io::Cursor::new(packet_data.as_slice());
125
126        let decompressed_data = if let Some(threshold) = self.compression {
127            let decompressed_len = VarInt::read(&mut cursor)?.0 as usize;
128            let raw_packet_len = packet_len - VarInt::written_size(decompressed_len as i32);
129
130            if decompressed_len > MAX_PACKET_DATA_SIZE {
131                Err(PacketError::TooLong(decompressed_len))?;
132            }
133
134            if decompressed_len > 0 {
135                // Decompress the remaining data
136                let mut decompressed = Vec::with_capacity(decompressed_len);
137                ZlibDecoder::new(&mut cursor)
138                    .read_to_end(&mut decompressed)
139                    .map_err(|e| PacketError::DecompressionFailed(e.to_string()))?;
140                decompressed
141            } else {
142                // Validate that we are not less than the compression threshold
143                if raw_packet_len > threshold.get() as _ {
144                    Err(PacketError::NotCompressed)?;
145                }
146
147                // Rest of the data is uncompressed
148                let pos = cursor.position() as usize;
149                packet_data[pos..].to_vec()
150            }
151        } else {
152            packet_data
153        };
154
155        // Parse packet ID and payload from decompressed data
156        let mut cursor = io::Cursor::new(decompressed_data.as_slice());
157        let packet_id = VarInt::read(&mut cursor)?.0;
158        let pos = cursor.position() as usize;
159        let payload = decompressed_data[pos..].to_vec();
160
161        Ok(RawPacket {
162            id: packet_id,
163            payload,
164        })
165    }
166}
167
168/* TODO: Tests.
169#[cfg(test)]
170mod tests {
171
172    use std::io::Write;
173
174    use super::*;
175    use aes::Aes128;
176    use cfb8::Encryptor as Cfb8Encryptor;
177    use cfb8::cipher::AsyncStreamCipher;
178    use flate2::Compression;
179    use flate2::write::ZlibEncoder;
180
181    /// Helper function to compress data using libdeflater's Zlib compressor
182    fn compress_zlib(data: &[u8]) -> Vec<u8> {
183        let mut compressed = Vec::new();
184        ZlibEncoder::new(&mut compressed, Compression::default())
185            .write_all(data)
186            .unwrap();
187        compressed
188    }
189
190    /// Helper function to encrypt data using AES-128 CFB-8 mode
191    fn encrypt_aes128(data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) {
192        let encryptor = Cfb8Encryptor::<Aes128>::new_from_slices(key, iv).expect("Invalid key/iv");
193        encryptor.encrypt(data);
194    }
195
196    /// Helper function to build a packet with optional compression and encryption
197    fn build_packet(
198        packet_id: i32,
199        payload: &[u8],
200        compress: bool,
201        key: Option<&[u8; 16]>,
202        iv: Option<&[u8; 16]>,
203    ) -> Vec<u8> {
204        let mut buffer = Vec::new();
205
206        if compress {
207            // Create a buffer that includes `packet_id_varint` and payload
208            let mut data_to_compress = Vec::new();
209            let packet_id_varint = VarInt(packet_id);
210            data_to_compress.write_var_int(&packet_id_varint).unwrap();
211            data_to_compress.write_slice(payload).unwrap();
212
213            // Compress the combined data
214            let compressed_payload = compress_zlib(&data_to_compress);
215            let data_len = data_to_compress.len() as i32; // 1 + payload.len()
216            let data_len_varint = VarInt(data_len);
217            buffer.write_var_int(&data_len_varint).unwrap();
218            buffer.write_slice(&compressed_payload).unwrap();
219        } else {
220            // No compression; `data_len` is payload length
221            let packet_id_varint = VarInt(packet_id);
222            buffer.write_var_int(&packet_id_varint).unwrap();
223            buffer.write_slice(payload).unwrap();
224        }
225
226        // Calculate packet length: length of buffer
227        let packet_len = buffer.len() as i32;
228        let packet_len_varint = VarInt(packet_len);
229        let mut packet_length_encoded = Vec::new();
230        {
231            packet_len_varint
232                .encode(&mut packet_length_encoded)
233                .unwrap();
234        }
235
236        // Create a new buffer for the entire packet
237        let mut packet = Vec::new();
238        packet.extend_from_slice(&packet_length_encoded);
239        packet.extend_from_slice(&buffer);
240
241        // Encrypt if key and IV are provided.
242        if let (Some(k), Some(v)) = (key, iv) {
243            encrypt_aes128(&mut packet, k, v);
244            packet
245        } else {
246            packet
247        }
248    }
249
250    /// Test decoding without compression and encryption
251    #[tokio::test]
252    async fn test_decode_without_compression_and_encryption() {
253        // Sample packet data: packet_id = 1, payload = "Hello"
254        let packet_id = 1;
255        let payload = b"Hello";
256
257        // Build the packet without compression and encryption
258        let packet = build_packet(packet_id, payload, false, None, None);
259
260        // Initialize the decoder without compression and encryption
261        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
262
263        // Attempt to decode
264        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
265
266        assert_eq!(raw_packet.id, packet_id);
267        assert_eq!(raw_packet.payload.as_ref(), payload);
268    }
269
270    /// Test decoding with compression
271    #[tokio::test]
272    async fn test_decode_with_compression() {
273        // Sample packet data: packet_id = 2, payload = "Hello, compressed world!"
274        let packet_id = 2;
275        let payload = b"Hello, compressed world!";
276
277        // Build the packet with compression enabled
278        let packet = build_packet(packet_id, payload, true, None, None);
279
280        // Initialize the decoder with compression enabled
281        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
282        // Larger than payload
283        decoder.set_compression(1000);
284
285        // Attempt to decode
286        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
287
288        assert_eq!(raw_packet.id, packet_id);
289        assert_eq!(raw_packet.payload.as_ref(), payload);
290    }
291
292    /// Test decoding with encryption
293    #[tokio::test]
294    async fn test_decode_with_encryption() {
295        // Sample packet data: packet_id = 3, payload = "Hello, encrypted world!"
296        let packet_id = 3;
297        let payload = b"Hello, encrypted world!";
298
299        // Define encryption key and IV
300        let key = [0x00u8; 16]; // Example key
301
302        // Build the packet with encryption enabled (no compression)
303        let packet = build_packet(packet_id, payload, false, Some(&key), Some(&key));
304
305        // Initialize the decoder with encryption enabled
306        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
307        decoder.set_encryption(&key);
308
309        // Attempt to decode
310        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
311
312        assert_eq!(raw_packet.id, packet_id);
313        assert_eq!(raw_packet.payload.as_ref(), payload);
314    }
315
316    /// Test decoding with both compression and encryption
317    #[tokio::test]
318    async fn test_decode_with_compression_and_encryption() {
319        // Sample packet data: packet_id = 4, payload = "Hello, compressed and encrypted world!"
320        let packet_id = 4;
321        let payload = b"Hello, compressed and encrypted world!";
322
323        // Define encryption key and IV
324        let key = [0x01u8; 16]; // Example key
325        let iv = [0x01u8; 16]; // Example IV
326
327        // Build the packet with both compression and encryption enabled
328        let packet = build_packet(packet_id, payload, true, Some(&key), Some(&iv));
329
330        // Initialize the decoder with both compression and encryption enabled
331        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
332        decoder.set_compression(1000);
333        decoder.set_encryption(&key);
334
335        // Attempt to decode
336        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
337
338        assert_eq!(raw_packet.id, packet_id);
339        assert_eq!(raw_packet.payload.as_ref(), payload);
340    }
341
342    /// Test decoding with invalid compressed data
343    #[tokio::test]
344    async fn test_decode_with_invalid_compressed_data() {
345        // Sample packet data: packet_id = 5, payload_len = 10, but compressed data is invalid
346        let data_len = 10; // Expected decompressed size
347        let invalid_compressed_data = vec![0xFF, 0xFF, 0xFF]; // Invalid Zlib data
348
349        // Build the packet with compression enabled but invalid compressed data
350        let mut buffer = Vec::new();
351        let data_len_varint = VarInt(data_len);
352        buffer.write_var_int(&data_len_varint).unwrap();
353        buffer.write_slice(&invalid_compressed_data).unwrap();
354
355        // Calculate packet length: VarInt(data_len) + invalid compressed data
356        let packet_len = buffer.len() as i32;
357        let packet_len_varint = VarInt(packet_len);
358
359        // Create a new buffer for the entire packet
360        let mut packet_buffer = Vec::new();
361        packet_buffer.write_var_int(&packet_len_varint).unwrap();
362        packet_buffer.write_slice(&buffer).unwrap();
363
364        let packet_bytes = packet_buffer;
365
366        // Initialize the decoder with compression enabled
367        let mut decoder = TCPNetworkDecoder::new(&packet_bytes[..]);
368        decoder.set_compression(1000);
369
370        // Attempt to decode and expect a decompression error
371        let result = decoder.get_raw_packet().await;
372
373        if result.is_ok() {
374            panic!("This should have errored!");
375        }
376    }
377
378    /// Test decoding with a zero-length packet
379    #[tokio::test]
380    async fn test_decode_with_zero_length_packet() {
381        // Sample packet data: packet_id = 7, payload = "" (empty)
382        let packet_id = 7;
383        let payload = b"";
384
385        // Build the packet without compression and encryption
386        let packet = build_packet(packet_id, payload, false, None, None);
387
388        // Initialize the decoder without compression and encryption
389        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
390
391        // Attempt to decode and expect a read error
392        let raw_packet = decoder.get_raw_packet().await.unwrap();
393        assert_eq!(raw_packet.id, packet_id);
394        assert_eq!(raw_packet.payload.as_ref(), payload);
395    }
396
397    /// Test decoding with maximum length packet
398    #[tokio::test]
399    async fn test_decode_with_maximum_length_packet() {
400        // Sample packet data: packet_id = 8, payload = "A" repeated MAX_PACKET_SIZE times
401        // Sample packet data: packet_id = 8, payload = "A" repeated (MAX_PACKET_SIZE - 1) times
402        let packet_id = 8;
403        let payload = vec![0x41u8; MAX_PACKET_SIZE as usize - 1]; // "A" repeated
404
405        // Build the packet with compression enabled
406        let packet = build_packet(packet_id, &payload, true, None, None);
407        println!("Built packet (with compression, maximum length): {packet:?}");
408
409        // Initialize the decoder with compression enabled
410        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
411        decoder.set_compression(MAX_PACKET_SIZE as usize + 1);
412
413        // Attempt to decode
414        let result = decoder.get_raw_packet().await;
415
416        let raw_packet = result.unwrap();
417        assert_eq!(raw_packet.id, packet_id);
418        assert_eq!(raw_packet.payload.as_ref(), payload);
419    }
420}
421 */