diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2026-01-13 10:51:45 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-13 10:51:45 -0600 |
| commit | b21ac946cafaacc9ee2478ea48ed9e72554f79ed (patch) | |
| tree | 4d05744b9801e94f5da6563d8fabddfb20d1c7b7 /azalea-buf/src/impls/mod.rs | |
| parent | d5fa5e32b37754b3b5c136e58821e48cd3b7c2ff (diff) | |
| download | azalea-drasl-b21ac946cafaacc9ee2478ea48ed9e72554f79ed.tar.xz | |
Merge AzaleaRead and AzaleaWrite (#305)
Diffstat (limited to 'azalea-buf/src/impls/mod.rs')
| -rw-r--r-- | azalea-buf/src/impls/mod.rs | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/azalea-buf/src/impls/mod.rs b/azalea-buf/src/impls/mod.rs new file mode 100644 index 00000000..37f53624 --- /dev/null +++ b/azalea-buf/src/impls/mod.rs @@ -0,0 +1,158 @@ +mod extra; +mod primitives; + +use std::{ + backtrace::Backtrace, + io::{self, Cursor, Write}, +}; + +use thiserror::Error; + +/// A trait that's implemented on types that are used by the Minecraft protocol. +pub trait AzBuf +where + Self: Sized, +{ + fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError>; + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()>; +} + +/// Used for types that have an alternative variable-length encoding. +/// +/// This mostly exists for varints. +pub trait AzBufVar +where + Self: Sized, +{ + fn azalea_read_var(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError>; + fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()>; +} + +/// Used for types that have some configurable limit. +/// +/// For example, the implementation of this on `String` limits the maximum +/// length of the string. +/// +/// This exists partially as an anti-abuse mechanism in Minecraft, so there is +/// no limited write function. +pub trait AzBufLimited +where + Self: Sized, +{ + fn azalea_read_limited(buf: &mut Cursor<&[u8]>, limit: u32) -> Result<Self, BufReadError>; +} + +#[derive(Debug, Error)] +pub enum BufReadError { + #[error("Invalid VarInt")] + InvalidVarInt, + #[error("Invalid VarLong")] + InvalidVarLong, + #[error("Error reading bytes")] + CouldNotReadBytes, + #[error( + "The received encoded string buffer length is longer than maximum allowed ({length} > {max_length})" + )] + StringLengthTooLong { length: u32, max_length: u32 }, + #[error("The received Vec length is longer than maximum allowed ({length} > {max_length})")] + VecLengthTooLong { length: u32, max_length: u32 }, + #[error("{source}")] + Io { + #[from] + #[backtrace] + source: io::Error, + }, + #[error("Invalid UTF-8: {bytes:?} (lossy: {lossy:?})")] + InvalidUtf8 { + bytes: Vec<u8>, + lossy: String, + // backtrace: Backtrace, + }, + #[error("Unexpected enum variant {id}")] + UnexpectedEnumVariant { id: i32 }, + #[error("Unexpected enum variant {id}")] + UnexpectedStringEnumVariant { id: String }, + #[error("Tried to read {attempted_read} bytes but there were only {actual_read}")] + UnexpectedEof { + attempted_read: usize, + actual_read: usize, + backtrace: Backtrace, + }, + #[error("{0}")] + Custom(String), + #[cfg(feature = "serde_json")] + #[error("{source}")] + Deserialization { + #[from] + #[backtrace] + source: serde_json::Error, + }, + #[error("{source}")] + Nbt { + #[from] + #[backtrace] + source: simdnbt::Error, + }, + #[error("{source}")] + DeserializeNbt { + #[from] + #[backtrace] + source: simdnbt::DeserializeError, + }, +} + +pub(crate) fn read_bytes<'a>( + buf: &'a mut Cursor<&[u8]>, + length: usize, +) -> Result<&'a [u8], BufReadError> { + if length > (buf.get_ref().len() - buf.position() as usize) { + return Err(BufReadError::UnexpectedEof { + attempted_read: length, + actual_read: buf.get_ref().len() - buf.position() as usize, + backtrace: Backtrace::capture(), + }); + } + let initial_position = buf.position() as usize; + buf.set_position(buf.position() + length as u64); + let data = &buf.get_ref()[initial_position..initial_position + length]; + Ok(data) +} + +pub(crate) fn read_utf_with_len<'a>( + buf: &'a mut Cursor<&[u8]>, + max_length: u32, +) -> Result<&'a str, BufReadError> { + let length = u32::azalea_read_var(buf)?; + // i don't know why it's multiplied by 4 but it's like that in mojang's code so + if length > max_length * 4 { + return Err(BufReadError::StringLengthTooLong { + length, + max_length: max_length * 4, + }); + } + + let buffer = read_bytes(buf, length as usize)?; + let string = std::str::from_utf8(buffer).map_err(|_| BufReadError::InvalidUtf8 { + bytes: buffer.to_vec(), + lossy: String::from_utf8_lossy(buffer).to_string(), + // backtrace: Backtrace::capture(), + })?; + if string.len() > length as usize { + return Err(BufReadError::StringLengthTooLong { length, max_length }); + } + + Ok(string) +} + +pub(crate) fn write_utf_with_len( + buf: &mut impl Write, + string: &str, + max_len: u32, +) -> io::Result<()> { + let actual_len = string.len(); + if actual_len > max_len as usize { + panic!("String too big (was {actual_len} bytes encoded, max {max_len})"); + } + string.as_bytes().to_vec().azalea_write(buf)?; + Ok(()) +} |
