1use std::{
5 io,
6 pin::Pin,
7 task::{Context, Poll},
8};
9
10use aes::cipher::{Array, BlockModeDecrypt, BlockModeEncrypt, BlockSizeUser};
11use thiserror::Error;
12use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
13
14pub type Aes128Cfb8Enc = cfb8::Encryptor<aes::Aes128>;
16pub type Aes128Cfb8Dec = cfb8::Decryptor<aes::Aes128>;
18
19pub const MAX_PACKET_SIZE: usize = 2_097_152;
21pub const MAX_PACKET_DATA_SIZE: usize = 8_388_608;
23
24#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
37pub enum ConnectionProtocol {
38 Handshake,
40 Play,
42 Status,
44 Login,
46 Config,
48}
49
50#[derive(Debug)]
52pub struct RawPacket {
53 pub id: i32,
55 buffer: Box<[u8]>,
56 payload_start: u32,
57}
58
59impl RawPacket {
60 #[must_use]
62 pub fn new(id: i32, payload: Vec<u8>) -> Self {
63 Self {
64 id,
65 buffer: payload.into_boxed_slice(),
66 payload_start: 0,
67 }
68 }
69
70 #[expect(
71 clippy::cast_possible_truncation,
72 reason = "packet buffers are limited to MAX_PACKET_DATA_SIZE bytes"
73 )]
74 pub(crate) fn from_buffer(id: i32, buffer: Vec<u8>, payload_start: usize) -> Self {
75 debug_assert!(payload_start <= buffer.len());
76 debug_assert!(payload_start <= MAX_PACKET_DATA_SIZE);
77 Self {
78 id,
79 buffer: buffer.into_boxed_slice(),
80 payload_start: payload_start as u32,
81 }
82 }
83
84 #[must_use]
86 pub fn payload(&self) -> &[u8] {
87 &self.buffer[self.payload_start as usize..]
88 }
89}
90
91#[derive(Error, Debug)]
93pub enum PacketError {
94 #[error("failed to decode packet ID")]
95 DecodeID,
97 #[error("packet length {0} exceeds maximum length")]
98 TooLong(usize),
100 #[error("packet length is out of bounds")]
101 OutOfBounds,
103 #[error("malformed packet length VarInt: {0}")]
104 MalformedLength(String),
106 #[error("malformed packet value: {0}")]
107 MalformedValue(String),
109 #[error("failed to decompress packet: {0}")]
110 DecompressionFailed(String),
112 #[error("failed to compress packet: {0}")]
113 CompressionFailed(String),
115 #[error("packet is uncompressed but greater than the threshold")]
116 NotCompressed,
118 #[error("failed to decrypt packet: {0}")]
119 DecryptionFailed(String),
121 #[error("failed to encrypt packet: {0}")]
122 EncryptionFailed(String),
124 #[error("the connection has closed")]
125 ConnectionClosed,
127 #[error("{0}")]
128 SendError(String),
130 #[error("Error: {0}")]
131 Other(String),
133 #[error("Invalid protocol: {0}")]
134 InvalidProtocol(String),
136}
137
138impl From<io::Error> for PacketError {
139 fn from(value: io::Error) -> Self {
140 Self::MalformedValue(value.to_string())
142 }
143}
144
145pub struct StreamEncryptor<W: AsyncWrite + Unpin> {
147 cipher: Aes128Cfb8Enc,
148 write: W,
149 last_unwritten_encrypted_byte: Option<u8>,
150}
151
152impl<W: AsyncWrite + Unpin> StreamEncryptor<W> {
153 pub fn new(cipher: Aes128Cfb8Enc, stream: W) -> Self {
155 debug_assert_eq!(Aes128Cfb8Enc::block_size(), 1);
156 Self {
157 cipher,
158 write: stream,
159 last_unwritten_encrypted_byte: None,
160 }
161 }
162}
163
164impl<W: AsyncWrite + Unpin> AsyncWrite for StreamEncryptor<W> {
165 #[expect(
166 clippy::unwrap_used,
167 reason = "CFB8 block size is one byte, so each chunk fits the cipher block type"
168 )]
169 fn poll_write(
170 self: Pin<&mut Self>,
171 cx: &mut Context<'_>,
172 buf: &[u8],
173 ) -> Poll<io::Result<usize>> {
174 let ref_self = self.get_mut();
175 let cipher = &mut ref_self.cipher;
176
177 let mut total_written = 0;
178 for block in buf.chunks(Aes128Cfb8Enc::block_size()) {
180 let mut out = [0u8];
181
182 if let Some(out_to_use) = ref_self.last_unwritten_encrypted_byte {
183 out[0] = out_to_use;
188 } else {
189 let out_block: &mut Array<u8, _> = (&mut out).into();
191 cipher.encrypt_block_b2b(block.try_into().unwrap(), out_block);
192 }
193
194 let write = Pin::new(&mut ref_self.write);
195 match write.poll_write(cx, &out) {
196 Poll::Pending => {
197 ref_self.last_unwritten_encrypted_byte = Some(out[0]);
198 if total_written == 0 {
199 return Poll::Pending;
201 }
202 return Poll::Ready(Ok(total_written));
204 }
205 Poll::Ready(result) => {
206 ref_self.last_unwritten_encrypted_byte = None;
207 match result {
208 Ok(written) => total_written += written,
209 Err(err) => return Poll::Ready(Err(err)),
210 }
211 }
212 }
213 }
214
215 Poll::Ready(Ok(total_written))
216 }
217
218 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
219 let ref_self = self.get_mut();
220 let write = Pin::new(&mut ref_self.write);
221 write.poll_flush(cx)
222 }
223
224 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
225 let ref_self = self.get_mut();
226 let write = Pin::new(&mut ref_self.write);
227 write.poll_shutdown(cx)
228 }
229}
230
231pub struct StreamDecryptor<R: AsyncRead + Unpin> {
233 cipher: Aes128Cfb8Dec,
234 read: R,
235}
236
237impl<R: AsyncRead + Unpin> StreamDecryptor<R> {
238 pub const fn new(cipher: Aes128Cfb8Dec, stream: R) -> Self {
240 Self {
241 cipher,
242 read: stream,
243 }
244 }
245}
246
247impl<R: AsyncRead + Unpin> AsyncRead for StreamDecryptor<R> {
248 #[expect(
249 clippy::unwrap_used,
250 reason = "CFB8 block size is one byte, so each chunk fits the cipher block type"
251 )]
252 fn poll_read(
253 self: Pin<&mut Self>,
254 cx: &mut Context<'_>,
255 buf: &mut ReadBuf<'_>,
256 ) -> Poll<io::Result<()>> {
257 let ref_self = self.get_mut();
258 let read = Pin::new(&mut ref_self.read);
259 let cipher = &mut ref_self.cipher;
260
261 let original_fill = buf.filled().len();
263 let internal_poll = read.poll_read(cx, buf);
265
266 if matches!(internal_poll, Poll::Ready(Ok(()))) {
267 for block in buf.filled_mut()[original_fill..].chunks_mut(Aes128Cfb8Dec::block_size()) {
269 cipher.decrypt_block(block.try_into().unwrap());
270 }
271 }
272
273 internal_poll
274 }
275}