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 pub payload: Vec<u8>,
57}
58
59#[derive(Error, Debug)]
61pub enum PacketError {
62 #[error("failed to decode packet ID")]
63 DecodeID,
65 #[error("packet length {0} exceeds maximum length")]
66 TooLong(usize),
68 #[error("packet length is out of bounds")]
69 OutOfBounds,
71 #[error("malformed packet length VarInt: {0}")]
72 MalformedLength(String),
74 #[error("malformed packet value: {0}")]
75 MalformedValue(String),
77 #[error("failed to decompress packet: {0}")]
78 DecompressionFailed(String),
80 #[error("failed to compress packet: {0}")]
81 CompressionFailed(String),
83 #[error("packet is uncompressed but greater than the threshold")]
84 NotCompressed,
86 #[error("failed to decrypt packet: {0}")]
87 DecryptionFailed(String),
89 #[error("failed to encrypt packet: {0}")]
90 EncryptionFailed(String),
92 #[error("the connection has closed")]
93 ConnectionClosed,
95 #[error("{0}")]
96 SendError(String),
98 #[error("Error: {0}")]
99 Other(String),
101 #[error("Invalid protocol: {0}")]
102 InvalidProtocol(String),
104}
105
106impl From<io::Error> for PacketError {
107 fn from(value: io::Error) -> Self {
108 Self::MalformedValue(value.to_string())
110 }
111}
112
113pub struct StreamEncryptor<W: AsyncWrite + Unpin> {
115 cipher: Aes128Cfb8Enc,
116 write: W,
117 last_unwritten_encrypted_byte: Option<u8>,
118}
119
120impl<W: AsyncWrite + Unpin> StreamEncryptor<W> {
121 pub fn new(cipher: Aes128Cfb8Enc, stream: W) -> Self {
123 debug_assert_eq!(Aes128Cfb8Enc::block_size(), 1);
124 Self {
125 cipher,
126 write: stream,
127 last_unwritten_encrypted_byte: None,
128 }
129 }
130}
131
132impl<W: AsyncWrite + Unpin> AsyncWrite for StreamEncryptor<W> {
133 #[expect(
134 clippy::unwrap_used,
135 reason = "CFB8 block size is one byte, so each chunk fits the cipher block type"
136 )]
137 fn poll_write(
138 self: Pin<&mut Self>,
139 cx: &mut Context<'_>,
140 buf: &[u8],
141 ) -> Poll<io::Result<usize>> {
142 let ref_self = self.get_mut();
143 let cipher = &mut ref_self.cipher;
144
145 let mut total_written = 0;
146 for block in buf.chunks(Aes128Cfb8Enc::block_size()) {
148 let mut out = [0u8];
149
150 if let Some(out_to_use) = ref_self.last_unwritten_encrypted_byte {
151 out[0] = out_to_use;
156 } else {
157 let out_block: &mut Array<u8, _> = (&mut out).into();
159 cipher.encrypt_block_b2b(block.try_into().unwrap(), out_block);
160 }
161
162 let write = Pin::new(&mut ref_self.write);
163 match write.poll_write(cx, &out) {
164 Poll::Pending => {
165 ref_self.last_unwritten_encrypted_byte = Some(out[0]);
166 if total_written == 0 {
167 return Poll::Pending;
169 }
170 return Poll::Ready(Ok(total_written));
172 }
173 Poll::Ready(result) => {
174 ref_self.last_unwritten_encrypted_byte = None;
175 match result {
176 Ok(written) => total_written += written,
177 Err(err) => return Poll::Ready(Err(err)),
178 }
179 }
180 }
181 }
182
183 Poll::Ready(Ok(total_written))
184 }
185
186 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
187 let ref_self = self.get_mut();
188 let write = Pin::new(&mut ref_self.write);
189 write.poll_flush(cx)
190 }
191
192 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
193 let ref_self = self.get_mut();
194 let write = Pin::new(&mut ref_self.write);
195 write.poll_shutdown(cx)
196 }
197}
198
199pub struct StreamDecryptor<R: AsyncRead + Unpin> {
201 cipher: Aes128Cfb8Dec,
202 read: R,
203}
204
205impl<R: AsyncRead + Unpin> StreamDecryptor<R> {
206 pub const fn new(cipher: Aes128Cfb8Dec, stream: R) -> Self {
208 Self {
209 cipher,
210 read: stream,
211 }
212 }
213}
214
215impl<R: AsyncRead + Unpin> AsyncRead for StreamDecryptor<R> {
216 #[expect(
217 clippy::unwrap_used,
218 reason = "CFB8 block size is one byte, so each chunk fits the cipher block type"
219 )]
220 fn poll_read(
221 self: Pin<&mut Self>,
222 cx: &mut Context<'_>,
223 buf: &mut ReadBuf<'_>,
224 ) -> Poll<io::Result<()>> {
225 let ref_self = self.get_mut();
226 let read = Pin::new(&mut ref_self.read);
227 let cipher = &mut ref_self.cipher;
228
229 let original_fill = buf.filled().len();
231 let internal_poll = read.poll_read(cx, buf);
233
234 if matches!(internal_poll, Poll::Ready(Ok(()))) {
235 for block in buf.filled_mut()[original_fill..].chunks_mut(Aes128Cfb8Dec::block_size()) {
237 cipher.decrypt_block(block.try_into().unwrap());
238 }
239 }
240
241 internal_poll
242 }
243}