Skip to main content

steel_protocol/
packet_writer.rs

1//! # Steel Protocol Packet Writer
2//!
3//! This module contains the implementation of the packet writer.
4/*
5Credit to https://github.com/Pumpkin-MC/Pumpkin/ for this implementation.
6*/
7
8use std::{
9    io,
10    pin::Pin,
11    task::{Context, Poll},
12};
13
14use aes::cipher::KeyIvInit;
15use thiserror::Error;
16use tokio::io::{AsyncWrite, AsyncWriteExt};
17
18use crate::{
19    packet_traits::EncodedPacket,
20    utils::{Aes128Cfb8Enc, PacketError, StreamEncryptor},
21};
22
23// raw -> compress -> encrypt
24/// A writer that can encrypt data.
25pub enum EncryptionWriter<W: AsyncWrite + Unpin> {
26    /// A writer that encrypts data.
27    Encrypt(Box<StreamEncryptor<W>>),
28    /// A writer that does not encrypt data.
29    None(W),
30}
31
32impl<W: AsyncWrite + Unpin> EncryptionWriter<W> {
33    /// Upgrades the writer to encrypt data.
34    ///
35    /// # Panics
36    /// - If the writer is already encrypting data.
37    #[must_use]
38    pub fn upgrade(self, cipher: Aes128Cfb8Enc) -> Self {
39        match self {
40            Self::None(stream) => Self::Encrypt(Box::new(StreamEncryptor::new(cipher, stream))),
41            Self::Encrypt(_) => panic!("Cannot upgrade a stream that already has a cipher!"),
42        }
43    }
44}
45
46impl<W: AsyncWrite + Unpin> AsyncWrite for EncryptionWriter<W> {
47    fn poll_write(
48        self: Pin<&mut Self>,
49        cx: &mut Context<'_>,
50        buf: &[u8],
51    ) -> Poll<io::Result<usize>> {
52        match self.get_mut() {
53            Self::Encrypt(writer) => {
54                let writer = Pin::new(writer);
55                writer.poll_write(cx, buf)
56            }
57            Self::None(writer) => {
58                let writer = Pin::new(writer);
59                writer.poll_write(cx, buf)
60            }
61        }
62    }
63
64    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
65        match self.get_mut() {
66            Self::Encrypt(writer) => {
67                let writer = Pin::new(writer);
68                writer.poll_flush(cx)
69            }
70            Self::None(writer) => {
71                let writer = Pin::new(writer);
72                writer.poll_flush(cx)
73            }
74        }
75    }
76
77    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
78        match self.get_mut() {
79            Self::Encrypt(writer) => {
80                let writer = Pin::new(writer);
81                writer.poll_shutdown(cx)
82            }
83            Self::None(writer) => {
84                let writer = Pin::new(writer);
85                writer.poll_shutdown(cx)
86            }
87        }
88    }
89}
90
91/// Encoder: Server -> Client
92/// Supports `ZLib` endecoding/compression
93/// Supports Aes128 Encryption
94pub struct TCPNetworkEncoder<W: AsyncWrite + Unpin> {
95    writer: EncryptionWriter<W>,
96}
97
98impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
99    /// Creates a new `TCPNetworkEncoder`.
100    pub const fn new(writer: W) -> Self {
101        Self {
102            writer: EncryptionWriter::None(writer),
103        }
104    }
105
106    /// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
107    ///
108    /// # Panics
109    /// - If the stream is already encrypted.
110    /// - If the key is invalid.
111    pub fn set_encryption(&mut self, key: &[u8; 16]) {
112        if matches!(self.writer, EncryptionWriter::Encrypt(_)) {
113            panic!("Cannot upgrade a stream that already has a cipher!");
114        }
115        let cipher = Aes128Cfb8Enc::new_from_slices(key, key).expect("invalid key");
116        replace_with::replace_with_or_abort(&mut self.writer, |encoder| encoder.upgrade(cipher));
117    }
118
119    /// Writes a packet to the stream.
120    ///
121    /// # Errors
122    /// - If the packet fails to write.
123    /// - If the stream fails to flush.
124    pub async fn write_packet(&mut self, packet: &EncodedPacket) -> Result<(), PacketError> {
125        self.writer
126            .write_all(&packet.encoded_data)
127            .await
128            .map_err(|e| PacketError::EncryptionFailed(e.to_string()))?;
129
130        self.writer
131            .flush()
132            .await
133            .map_err(|e| PacketError::EncryptionFailed(e.to_string()))
134    }
135}
136
137/// An error that occurs when the compression level is invalid.
138#[derive(Error, Debug)]
139#[error("Invalid compression Level")]
140pub struct CompressionLevelError;
141
142/* TODO: Tests.
143#[cfg(test)]
144mod tests {
145    use std::io::Read;
146
147    use super::*;
148    use crate::java::client::status::CStatusResponse;
149    use crate::packet::Packet;
150    use crate::{ClientPacket, ReadingError};
151    use aes::Aes128;
152    use cfb8::Decryptor as Cfb8Decryptor;
153    use cfb8::cipher::AsyncStreamCipher;
154    use flate2::read::ZlibDecoder;
155    use pumpkin_data::packet::client:STATUS_STATUS_RESPONSE;
156    use pumpkin_macros::packet;
157    use serde::Serialize;
158
159    /// Define a custom packet for testing maximum packet size
160    #[derive(Serialize)]
161    #[packet(STATUS_STATUS_RESPONSE)]
162    pub struct MaxSizePacket {
163        data: Vec<u8>,
164    }
165
166    impl MaxSizePacket {
167        pub fn new(size: usize) -> Self {
168            Self {
169                data: vec![0xAB; size], // Fill with arbitrary data
170            }
171        }
172    }
173
174    /// Helper function to decode a `VarInt` from bytes
175    fn decode_varint(buffer: &mut &[u8]) -> Result<i32, ReadingError> {
176        Ok(buffer.get_var_int()?.0)
177    }
178
179    /// Helper function to decompress data using libdeflater's Zlib decompressor
180    fn decompress_zlib(data: &[u8], expected_size: usize) -> Result<Vec<u8>, std::io::Error> {
181        assert!(!data.is_empty());
182        let mut decompressed = vec![0u8; expected_size];
183        ZlibDecoder::new(data).read_exact(&mut decompressed)?;
184        Ok(decompressed)
185    }
186
187    /// Helper function to decrypt data using AES-128 CFB-8 mode
188    fn decrypt_aes128(encrypted_data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) {
189        let decryptor = Cfb8Decryptor::<Aes128>::new_from_slices(key, iv).expect("Invalid key/iv");
190        decryptor.decrypt(encrypted_data);
191    }
192
193    /// Helper function to build a packet with optional compression and encryption
194    async fn build_packet_with_encoder<T: ClientPacket>(
195        packet: &T,
196        compression_info: Option<(CompressionThreshold, CompressionLevel)>,
197        key: Option<&[u8; 16]>,
198    ) -> Box<[u8]> {
199        let mut buf = Vec::new();
200        let mut encoder = TCPNetworkEncoder::new(&mut buf);
201        if let Some(compression_info) = compression_info {
202            encoder.set_compression(compression_info);
203        }
204
205        if let Some(key) = key {
206            encoder.set_encryption(key);
207        }
208
209        let mut packet_buf = Vec::new();
210        let writer = &mut packet_buf;
211        writer.write_var_int(&VarInt(T::PACKET_ID)).unwrap();
212        packet.write_packet_data(writer).unwrap();
213
214        encoder.write_packet(packet_buf.into()).await.unwrap();
215
216        buf.into_boxed_slice()
217    }
218
219    /// Test encoding without compression and encryption
220    #[tokio::test]
221    async fn test_encode_without_compression_and_encryption() {
222        // Create a CStatusResponse packet
223        let packet =
224            CStatusResponse::new(String::from("{\"description\": \"A Minecraft Server\"}"));
225
226        // Build the packet without compression and encryption
227        let packet_bytes = build_packet_with_encoder(&packet, None, None).await;
228
229        // Decode the packet manually to verify correctness
230        let mut buffer = &packet_bytes[..];
231
232        // Read packet length VarInt
233        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
234        assert_eq!(
235            packet_length as usize,
236            buffer.len(),
237            "Packet length mismatch"
238        );
239
240        // Read packet ID VarInt
241        let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
242        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
243
244        // Remaining buffer is the payload
245        // We need to obtain the expected payload
246        let mut expected_payload = Vec::new();
247        packet.write_packet_data(&mut expected_payload).unwrap();
248
249        assert_eq!(buffer, expected_payload);
250    }
251
252    /// Test encoding with compression
253    #[tokio::test]
254    async fn test_encode_with_compression() {
255        // Create a CStatusResponse packet
256        let packet =
257            CStatusResponse::new("{\"description\": \"A Minecraft Server\"}".parse().unwrap());
258
259        // Build the packet with compression enabled
260        let packet_bytes = build_packet_with_encoder(&packet, Some((0, 6)), None).await;
261
262        // Decode the packet manually to verify correctness
263        let mut buffer = &packet_bytes[..];
264
265        // Read packet length VarInt
266        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
267        assert_eq!(
268            packet_length as usize,
269            buffer.len(),
270            "Packet length mismatch"
271        );
272
273        // Read data length VarInt (uncompressed data length)
274        let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
275        let mut expected_payload = Vec::new();
276        packet.write_packet_data(&mut expected_payload).unwrap();
277        let uncompressed_data_length =
278            VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len();
279        assert_eq!(data_length as usize, uncompressed_data_length);
280
281        // Remaining buffer is the compressed data
282        let compressed_data = buffer;
283
284        // Decompress the data
285        let decompressed_data = decompress_zlib(compressed_data, data_length as usize)
286            .expect("Failed to decompress data");
287
288        // Verify packet ID and payload
289        let mut decompressed_buffer = &decompressed_data[..];
290
291        // Read packet ID VarInt
292        let decoded_packet_id =
293            decode_varint(&mut decompressed_buffer).expect("Failed to decode packet ID");
294        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
295
296        // Remaining buffer is the payload
297        assert_eq!(decompressed_buffer, expected_payload);
298    }
299
300    /// Test encoding with encryption
301    #[tokio::test]
302    async fn test_encode_with_encryption() {
303        // Create a CStatusResponse packet
304        let packet =
305            CStatusResponse::new("{\"description\": \"A Minecraft Server\"}".parse().unwrap());
306
307        // Encryption key and IV (IV is the same as key in this case)
308        let key = [0x00u8; 16]; // Example key
309
310        // Build the packet with encryption enabled (no compression)
311        let mut packet_bytes = build_packet_with_encoder(&packet, None, Some(&key)).await;
312
313        // Decrypt the packet
314        decrypt_aes128(&mut packet_bytes, &key, &key);
315
316        // Decode the packet manually to verify correctness
317        let mut buffer = &packet_bytes[..];
318
319        // Read packet length VarInt
320        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
321        assert_eq!(
322            packet_length as usize,
323            buffer.len(),
324            "Packet length mismatch"
325        );
326
327        // Read packet ID VarInt
328        let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
329        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
330
331        // Remaining buffer is the payload
332        let mut expected_payload = Vec::new();
333        packet.write_packet_data(&mut expected_payload).unwrap();
334        assert_eq!(buffer, expected_payload);
335    }
336
337    /// Test encoding with both compression and encryption
338    #[tokio::test]
339    async fn test_encode_with_compression_and_encryption() {
340        // Create a CStatusResponse packet
341        let packet =
342            CStatusResponse::new("{\"description\": \"A Minecraft Server\"}".parse().unwrap());
343
344        // Encryption key and IV (IV is the same as key in this case)
345        let key = [0x01u8; 16]; // Example key
346
347        // Build the packet with both compression and encryption enabled
348        // Compression threshold is set to 0 to force compression
349        let mut packet_bytes = build_packet_with_encoder(&packet, Some((0, 6)), Some(&key)).await;
350
351        // Decrypt the packet
352        decrypt_aes128(&mut packet_bytes, &key, &key);
353
354        // Decode the packet manually to verify correctness
355        let mut buffer = &packet_bytes[..];
356
357        // Read packet length VarInt
358        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
359        assert_eq!(
360            packet_length as usize,
361            buffer.len(),
362            "Packet length mismatch"
363        );
364
365        // Read data length VarInt (uncompressed data length)
366        let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
367        let mut expected_payload = Vec::new();
368        packet.write_packet_data(&mut expected_payload).unwrap();
369        let uncompressed_data_length =
370            VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len();
371        assert_eq!(data_length as usize, uncompressed_data_length);
372
373        // Remaining buffer is the compressed data
374        let compressed_data = buffer;
375
376        // Decompress the data
377        let decompressed_data = decompress_zlib(compressed_data, data_length as usize)
378            .expect("Failed to decompress data");
379
380        // Verify packet ID and payload
381        let mut decompressed_buffer = &decompressed_data[..];
382
383        // Read packet ID VarInt
384        let decoded_packet_id =
385            decode_varint(&mut decompressed_buffer).expect("Failed to decode packet ID");
386        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
387
388        // Remaining buffer is the payload
389        assert_eq!(decompressed_buffer, expected_payload);
390    }
391
392    /// Test encoding with zero-length payload
393    #[tokio::test]
394    async fn test_encode_with_zero_length_payload() {
395        // Create a CStatusResponse packet with empty payload
396        let packet = CStatusResponse::new(String::from(""));
397
398        // Build the packet without compression and encryption
399        let packet_bytes = build_packet_with_encoder(&packet, None, None).await;
400
401        // Decode the packet manually to verify correctness
402        let mut buffer = &packet_bytes[..];
403
404        // Read packet length VarInt
405        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
406        assert_eq!(
407            packet_length as usize,
408            buffer.len(),
409            "Packet length mismatch"
410        );
411
412        // Read packet ID VarInt
413        let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
414        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
415
416        // Remaining buffer is the payload (empty)
417        let mut expected_payload = Vec::new();
418        packet.write_packet_data(&mut expected_payload).unwrap();
419
420        assert_eq!(
421            buffer.len(),
422            expected_payload.len(),
423            "Payload length mismatch"
424        );
425        assert_eq!(buffer, expected_payload);
426    }
427
428    /// Test encoding with maximum length payload
429    #[tokio::test]
430    async fn test_encode_with_maximum_string_length() {
431        // Maximum allowed string length is 32767 bytes
432        let max_string_length = 32767;
433        let payload_str = "A".repeat(max_string_length);
434        let packet = CStatusResponse::new(payload_str);
435
436        // Build the packet without compression and encryption
437        let packet_bytes = build_packet_with_encoder(&packet, None, None).await;
438
439        // Verify that the packet size does not exceed MAX_PACKET_SIZE as usize
440        assert!(
441            packet_bytes.len() <= MAX_PACKET_SIZE as usize,
442            "Packet size exceeds maximum allowed size"
443        );
444
445        // Decode the packet manually to verify correctness
446        let mut buffer = &packet_bytes[..];
447
448        // Read packet length VarInt
449        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
450        assert_eq!(
451            packet_length as usize,
452            buffer.len(),
453            "Packet length mismatch"
454        );
455
456        // Read packet ID VarInt
457        let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
458        // Assume packet ID is 0 for CStatusResponse
459        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
460
461        // Remaining buffer is the payload
462        let mut expected_payload = Vec::new();
463        packet.write_packet_data(&mut expected_payload).unwrap();
464
465        assert_eq!(buffer, expected_payload);
466    }
467
468    /// Test encoding a packet that exceeds MAX_PACKET_SIZE as usize
469    #[tokio::test]
470    #[should_panic(expected = "TooLong")]
471    async fn test_encode_packet_exceeding_maximum_size() {
472        // Create a custom packet with data exceeding MAX_PACKET_SIZE as usize
473        let data_size = MAX_PACKET_SIZE as usize + 1; // Exceed by 1 byte
474        let packet = MaxSizePacket::new(data_size);
475
476        // Build the packet without compression and encryption
477        // This should panic with PacketEncodeError::TooLong
478        build_packet_with_encoder(&packet, None, None).await;
479    }
480
481    /// Test encoding with a small payload that should not be compressed
482    #[tokio::test]
483    async fn test_encode_small_payload_no_compression() {
484        // Create a CStatusResponse packet with small payload
485        let packet = CStatusResponse::new(String::from("Hi"));
486
487        // Build the packet with compression enabled
488        // Compression threshold is set to a value higher than payload length
489        let packet_bytes = build_packet_with_encoder(&packet, Some((10, 6)), None).await;
490
491        // Decode the packet manually to verify that it was not compressed
492        let mut buffer = &packet_bytes[..];
493
494        // Read packet length VarInt
495        let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
496        assert_eq!(
497            packet_length as usize,
498            buffer.len(),
499            "Packet length mismatch"
500        );
501
502        // Read data length VarInt (should be 0 indicating no compression)
503        let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
504        assert_eq!(
505            data_length, 0,
506            "Data length should be 0 indicating no compression"
507        );
508
509        // Read packet ID VarInt
510        let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
511        assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
512
513        // Remaining buffer is the payload
514        let mut expected_payload = Vec::new();
515        packet.write_packet_data(&mut expected_payload).unwrap();
516
517        assert_eq!(buffer, expected_payload);
518    }
519}
520*/