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 (packet_buffer, packet_data_start) = 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                let mut decompressed = vec![0; decompressed_len];
136                let mut decoder = ZlibDecoder::new(&mut cursor);
137                decoder
138                    .read_exact(&mut decompressed)
139                    .map_err(|e| PacketError::DecompressionFailed(e.to_string()))?;
140
141                let mut overflow = [0];
142                if decoder
143                    .read(&mut overflow)
144                    .map_err(|e| PacketError::DecompressionFailed(e.to_string()))?
145                    != 0
146                {
147                    Err(PacketError::DecompressionFailed(format!(
148                        "decompressed packet exceeds declared length of {decompressed_len}"
149                    )))?;
150                }
151                (decompressed, 0)
152            } else {
153                // Validate that we are not less than the compression threshold
154                if raw_packet_len > threshold.get() as _ {
155                    Err(PacketError::NotCompressed)?;
156                }
157
158                // The rest of the packet data is uncompressed.
159                let packet_data_start = cursor.position() as usize;
160                (packet_data, packet_data_start)
161            }
162        } else {
163            (packet_data, 0)
164        };
165
166        // Parse the packet ID while retaining the buffer that already owns the payload.
167        let mut cursor = io::Cursor::new(&packet_buffer[packet_data_start..]);
168        let packet_id = VarInt::read(&mut cursor)?.0;
169        let payload_start = packet_data_start + cursor.position() as usize;
170
171        Ok(RawPacket::from_buffer(
172            packet_id,
173            packet_buffer,
174            payload_start,
175        ))
176    }
177}
178
179#[cfg(test)]
180mod payload_tests {
181    use std::{io::Write as _, num::NonZeroU32};
182
183    use flate2::{Compression, write::ZlibEncoder};
184
185    use super::TCPNetworkDecoder;
186
187    const VARINT_DATA_MASK: u8 = 0x7f;
188    const VARINT_CONTINUE_BIT: u8 = 0x80;
189
190    fn write_varint(mut value: u32, output: &mut Vec<u8>) {
191        loop {
192            if value <= u32::from(VARINT_DATA_MASK) {
193                output.push(value as u8);
194                return;
195            }
196            output.push((value as u8 & VARINT_DATA_MASK) | VARINT_CONTINUE_BIT);
197            value >>= 7;
198        }
199    }
200
201    fn packet_data(packet_id: u32, payload: &[u8]) -> Vec<u8> {
202        let mut data = Vec::with_capacity(payload.len() + 5);
203        write_varint(packet_id, &mut data);
204        data.extend_from_slice(payload);
205        data
206    }
207
208    fn frame(data: &[u8]) -> Vec<u8> {
209        let mut framed = Vec::with_capacity(data.len() + 5);
210        write_varint(data.len() as u32, &mut framed);
211        framed.extend_from_slice(data);
212        framed
213    }
214
215    #[tokio::test]
216    async fn payload_slice_starts_after_framing_and_packet_id() {
217        let packet_id = 300;
218        let payload = b"payload bytes";
219        let frame = frame(&packet_data(packet_id, payload));
220        let mut decoder = TCPNetworkDecoder::new(frame.as_slice());
221
222        let packet = decoder.get_raw_packet().await.expect("decode plain packet");
223
224        assert_eq!(packet.id, packet_id as i32);
225        assert_eq!(packet.payload(), payload);
226    }
227
228    #[tokio::test]
229    async fn uncompressed_payload_slice_skips_compression_length() {
230        let packet_id = 300;
231        let payload = b"payload bytes";
232        let packet_data = packet_data(packet_id, payload);
233        let mut compression_data = Vec::with_capacity(packet_data.len() + 1);
234        write_varint(0, &mut compression_data);
235        compression_data.extend_from_slice(&packet_data);
236        let frame = frame(&compression_data);
237        let mut decoder = TCPNetworkDecoder::new(frame.as_slice());
238        decoder.set_compression(NonZeroU32::new(256).expect("nonzero threshold"));
239
240        let packet = decoder
241            .get_raw_packet()
242            .await
243            .expect("decode uncompressed packet");
244
245        assert_eq!(packet.id, packet_id as i32);
246        assert_eq!(packet.payload(), payload);
247    }
248
249    #[tokio::test]
250    async fn decompressed_payload_slice_starts_after_packet_id() {
251        let packet_id = 300;
252        let payload = vec![0x5a; 1_024];
253        let packet_data = packet_data(packet_id, &payload);
254        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::fast());
255        encoder
256            .write_all(&packet_data)
257            .expect("compress packet data");
258        let compressed = encoder.finish().expect("finish packet compression");
259        let mut compression_data = Vec::with_capacity(compressed.len() + 5);
260        write_varint(packet_data.len() as u32, &mut compression_data);
261        compression_data.extend_from_slice(&compressed);
262        let frame = frame(&compression_data);
263        let mut decoder = TCPNetworkDecoder::new(frame.as_slice());
264        decoder.set_compression(NonZeroU32::new(256).expect("nonzero threshold"));
265
266        let packet = decoder
267            .get_raw_packet()
268            .await
269            .expect("decode compressed packet");
270
271        assert_eq!(packet.id, packet_id as i32);
272        assert_eq!(packet.payload(), payload);
273    }
274}
275
276/* TODO: Tests.
277#[cfg(test)]
278mod tests {
279
280    use std::io::Write;
281
282    use super::*;
283    use aes::Aes128;
284    use cfb8::Encryptor as Cfb8Encryptor;
285    use cfb8::cipher::AsyncStreamCipher;
286    use flate2::Compression;
287    use flate2::write::ZlibEncoder;
288
289    /// Helper function to compress data using libdeflater's Zlib compressor
290    fn compress_zlib(data: &[u8]) -> Vec<u8> {
291        let mut compressed = Vec::new();
292        ZlibEncoder::new(&mut compressed, Compression::default())
293            .write_all(data)
294            .unwrap();
295        compressed
296    }
297
298    /// Helper function to encrypt data using AES-128 CFB-8 mode
299    fn encrypt_aes128(data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) {
300        let encryptor = Cfb8Encryptor::<Aes128>::new_from_slices(key, iv).expect("Invalid key/iv");
301        encryptor.encrypt(data);
302    }
303
304    /// Helper function to build a packet with optional compression and encryption
305    fn build_packet(
306        packet_id: i32,
307        payload: &[u8],
308        compress: bool,
309        key: Option<&[u8; 16]>,
310        iv: Option<&[u8; 16]>,
311    ) -> Vec<u8> {
312        let mut buffer = Vec::new();
313
314        if compress {
315            // Create a buffer that includes `packet_id_varint` and payload
316            let mut data_to_compress = Vec::new();
317            let packet_id_varint = VarInt(packet_id);
318            data_to_compress.write_var_int(&packet_id_varint).unwrap();
319            data_to_compress.write_slice(payload).unwrap();
320
321            // Compress the combined data
322            let compressed_payload = compress_zlib(&data_to_compress);
323            let data_len = data_to_compress.len() as i32; // 1 + payload.len()
324            let data_len_varint = VarInt(data_len);
325            buffer.write_var_int(&data_len_varint).unwrap();
326            buffer.write_slice(&compressed_payload).unwrap();
327        } else {
328            // No compression; `data_len` is payload length
329            let packet_id_varint = VarInt(packet_id);
330            buffer.write_var_int(&packet_id_varint).unwrap();
331            buffer.write_slice(payload).unwrap();
332        }
333
334        // Calculate packet length: length of buffer
335        let packet_len = buffer.len() as i32;
336        let packet_len_varint = VarInt(packet_len);
337        let mut packet_length_encoded = Vec::new();
338        {
339            packet_len_varint
340                .encode(&mut packet_length_encoded)
341                .unwrap();
342        }
343
344        // Create a new buffer for the entire packet
345        let mut packet = Vec::new();
346        packet.extend_from_slice(&packet_length_encoded);
347        packet.extend_from_slice(&buffer);
348
349        // Encrypt if key and IV are provided.
350        if let (Some(k), Some(v)) = (key, iv) {
351            encrypt_aes128(&mut packet, k, v);
352            packet
353        } else {
354            packet
355        }
356    }
357
358    /// Test decoding without compression and encryption
359    #[tokio::test]
360    async fn test_decode_without_compression_and_encryption() {
361        // Sample packet data: packet_id = 1, payload = "Hello"
362        let packet_id = 1;
363        let payload = b"Hello";
364
365        // Build the packet without compression and encryption
366        let packet = build_packet(packet_id, payload, false, None, None);
367
368        // Initialize the decoder without compression and encryption
369        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
370
371        // Attempt to decode
372        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
373
374        assert_eq!(raw_packet.id, packet_id);
375        assert_eq!(raw_packet.payload.as_ref(), payload);
376    }
377
378    /// Test decoding with compression
379    #[tokio::test]
380    async fn test_decode_with_compression() {
381        // Sample packet data: packet_id = 2, payload = "Hello, compressed world!"
382        let packet_id = 2;
383        let payload = b"Hello, compressed world!";
384
385        // Build the packet with compression enabled
386        let packet = build_packet(packet_id, payload, true, None, None);
387
388        // Initialize the decoder with compression enabled
389        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
390        // Larger than payload
391        decoder.set_compression(1000);
392
393        // Attempt to decode
394        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
395
396        assert_eq!(raw_packet.id, packet_id);
397        assert_eq!(raw_packet.payload.as_ref(), payload);
398    }
399
400    /// Test decoding with encryption
401    #[tokio::test]
402    async fn test_decode_with_encryption() {
403        // Sample packet data: packet_id = 3, payload = "Hello, encrypted world!"
404        let packet_id = 3;
405        let payload = b"Hello, encrypted world!";
406
407        // Define encryption key and IV
408        let key = [0x00u8; 16]; // Example key
409
410        // Build the packet with encryption enabled (no compression)
411        let packet = build_packet(packet_id, payload, false, Some(&key), Some(&key));
412
413        // Initialize the decoder with encryption enabled
414        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
415        decoder.set_encryption(&key);
416
417        // Attempt to decode
418        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
419
420        assert_eq!(raw_packet.id, packet_id);
421        assert_eq!(raw_packet.payload.as_ref(), payload);
422    }
423
424    /// Test decoding with both compression and encryption
425    #[tokio::test]
426    async fn test_decode_with_compression_and_encryption() {
427        // Sample packet data: packet_id = 4, payload = "Hello, compressed and encrypted world!"
428        let packet_id = 4;
429        let payload = b"Hello, compressed and encrypted world!";
430
431        // Define encryption key and IV
432        let key = [0x01u8; 16]; // Example key
433        let iv = [0x01u8; 16]; // Example IV
434
435        // Build the packet with both compression and encryption enabled
436        let packet = build_packet(packet_id, payload, true, Some(&key), Some(&iv));
437
438        // Initialize the decoder with both compression and encryption enabled
439        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
440        decoder.set_compression(1000);
441        decoder.set_encryption(&key);
442
443        // Attempt to decode
444        let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
445
446        assert_eq!(raw_packet.id, packet_id);
447        assert_eq!(raw_packet.payload.as_ref(), payload);
448    }
449
450    /// Test decoding with invalid compressed data
451    #[tokio::test]
452    async fn test_decode_with_invalid_compressed_data() {
453        // Sample packet data: packet_id = 5, payload_len = 10, but compressed data is invalid
454        let data_len = 10; // Expected decompressed size
455        let invalid_compressed_data = vec![0xFF, 0xFF, 0xFF]; // Invalid Zlib data
456
457        // Build the packet with compression enabled but invalid compressed data
458        let mut buffer = Vec::new();
459        let data_len_varint = VarInt(data_len);
460        buffer.write_var_int(&data_len_varint).unwrap();
461        buffer.write_slice(&invalid_compressed_data).unwrap();
462
463        // Calculate packet length: VarInt(data_len) + invalid compressed data
464        let packet_len = buffer.len() as i32;
465        let packet_len_varint = VarInt(packet_len);
466
467        // Create a new buffer for the entire packet
468        let mut packet_buffer = Vec::new();
469        packet_buffer.write_var_int(&packet_len_varint).unwrap();
470        packet_buffer.write_slice(&buffer).unwrap();
471
472        let packet_bytes = packet_buffer;
473
474        // Initialize the decoder with compression enabled
475        let mut decoder = TCPNetworkDecoder::new(&packet_bytes[..]);
476        decoder.set_compression(1000);
477
478        // Attempt to decode and expect a decompression error
479        let result = decoder.get_raw_packet().await;
480
481        if result.is_ok() {
482            panic!("This should have errored!");
483        }
484    }
485
486    /// Test decoding with a zero-length packet
487    #[tokio::test]
488    async fn test_decode_with_zero_length_packet() {
489        // Sample packet data: packet_id = 7, payload = "" (empty)
490        let packet_id = 7;
491        let payload = b"";
492
493        // Build the packet without compression and encryption
494        let packet = build_packet(packet_id, payload, false, None, None);
495
496        // Initialize the decoder without compression and encryption
497        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
498
499        // Attempt to decode and expect a read error
500        let raw_packet = decoder.get_raw_packet().await.unwrap();
501        assert_eq!(raw_packet.id, packet_id);
502        assert_eq!(raw_packet.payload.as_ref(), payload);
503    }
504
505    /// Test decoding with maximum length packet
506    #[tokio::test]
507    async fn test_decode_with_maximum_length_packet() {
508        // Sample packet data: packet_id = 8, payload = "A" repeated MAX_PACKET_SIZE times
509        // Sample packet data: packet_id = 8, payload = "A" repeated (MAX_PACKET_SIZE - 1) times
510        let packet_id = 8;
511        let payload = vec![0x41u8; MAX_PACKET_SIZE as usize - 1]; // "A" repeated
512
513        // Build the packet with compression enabled
514        let packet = build_packet(packet_id, &payload, true, None, None);
515        println!("Built packet (with compression, maximum length): {packet:?}");
516
517        // Initialize the decoder with compression enabled
518        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
519        decoder.set_compression(MAX_PACKET_SIZE as usize + 1);
520
521        // Attempt to decode
522        let result = decoder.get_raw_packet().await;
523
524        let raw_packet = result.unwrap();
525        assert_eq!(raw_packet.id, packet_id);
526        assert_eq!(raw_packet.payload.as_ref(), payload);
527    }
528}
529 */
530
531#[cfg(test)]
532mod compression_security_tests {
533    use std::io::Write;
534
535    use flate2::{Compression, write::ZlibEncoder};
536    use steel_utils::serial::WriteTo;
537
538    use super::*;
539
540    fn compressed_packet(claimed_len: usize, decompressed: &[u8]) -> Vec<u8> {
541        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
542        encoder
543            .write_all(decompressed)
544            .expect("test payload compression should succeed");
545        let compressed = encoder
546            .finish()
547            .expect("test payload compression should finish");
548
549        let mut packet_data = Vec::new();
550        VarInt::from(claimed_len)
551            .write(&mut packet_data)
552            .expect("test claimed length should encode");
553        packet_data.extend_from_slice(&compressed);
554
555        let mut packet = Vec::new();
556        VarInt::from(packet_data.len())
557            .write(&mut packet)
558            .expect("test packet length should encode");
559        packet.extend_from_slice(&packet_data);
560        packet
561    }
562
563    #[tokio::test]
564    async fn rejects_zlib_stream_expanding_past_declared_length() {
565        // This models a tiny wire payload claiming one byte while expanding to twice the protocol
566        // maximum.
567        let expanded = vec![0; MAX_PACKET_DATA_SIZE * 2];
568        let packet = compressed_packet(1, &expanded);
569        assert!(packet.len() < 32 * 1024);
570
571        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
572        decoder.set_compression(NonZeroU32::MIN);
573
574        assert!(matches!(
575            decoder.get_raw_packet().await,
576            Err(PacketError::DecompressionFailed(_))
577        ));
578    }
579
580    #[tokio::test]
581    async fn rejects_zlib_stream_shorter_than_declared_length() {
582        let packet = compressed_packet(2, &[0]);
583        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
584        decoder.set_compression(NonZeroU32::MIN);
585
586        assert!(matches!(
587            decoder.get_raw_packet().await,
588            Err(PacketError::DecompressionFailed(_))
589        ));
590    }
591
592    #[tokio::test]
593    async fn accepts_zlib_stream_matching_declared_length() {
594        let decompressed = [2, b'h', b'e', b'l', b'l', b'o'];
595        let packet = compressed_packet(decompressed.len(), &decompressed);
596        let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
597        decoder.set_compression(NonZeroU32::MIN);
598
599        let raw_packet = decoder
600            .get_raw_packet()
601            .await
602            .expect("valid compressed packet should decode");
603
604        assert_eq!(raw_packet.id, 2);
605        assert_eq!(raw_packet.payload(), b"hello");
606    }
607}