aboutsummaryrefslogtreecommitdiff
path: root/azalea-buf/src/impls/mod.rs
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2026-01-13 10:51:45 -0600
committerGitHub <noreply@github.com>2026-01-13 10:51:45 -0600
commitb21ac946cafaacc9ee2478ea48ed9e72554f79ed (patch)
tree4d05744b9801e94f5da6563d8fabddfb20d1c7b7 /azalea-buf/src/impls/mod.rs
parentd5fa5e32b37754b3b5c136e58821e48cd3b7c2ff (diff)
downloadazalea-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.rs158
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(())
+}