Skip to main content

azalea_buf/impls/
mod.rs

1#![allow(clippy::redundant_field_names, reason = "Triggered by `Error` derive")]
2
3mod extra;
4mod primitives;
5
6use std::{
7    backtrace::Backtrace,
8    io::{self, Cursor, Write},
9};
10
11use thiserror::Error;
12
13/// A trait that's implemented on types that are used by the Minecraft protocol.
14pub trait AzBuf
15where
16    Self: Sized,
17{
18    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError>;
19    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()>;
20}
21
22/// Used for types that have an alternative variable-length encoding.
23///
24/// This mostly exists for varints.
25pub trait AzBufVar
26where
27    Self: Sized,
28{
29    fn azalea_read_var(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError>;
30    fn azalea_write_var(&self, buf: &mut impl Write) -> io::Result<()>;
31}
32
33/// Used for types that have some configurable limit.
34///
35/// For example, the implementation of this on `String` limits the maximum
36/// length of the string.
37///
38/// This exists partially as an anti-abuse mechanism in Minecraft, so there is
39/// no limited write function.
40pub trait AzBufLimited
41where
42    Self: Sized,
43{
44    fn azalea_read_limited(buf: &mut Cursor<&[u8]>, limit: u32) -> Result<Self, BufReadError>;
45}
46
47#[derive(Debug, Error)]
48pub enum BufReadError {
49    #[error("Invalid VarInt")]
50    InvalidVarInt,
51    #[error("Invalid VarLong")]
52    InvalidVarLong,
53    #[error("Error reading bytes")]
54    CouldNotReadBytes,
55    #[error(
56        "The received encoded string buffer length is longer than maximum allowed ({length} > {max_length})"
57    )]
58    StringLengthTooLong { length: u32, max_length: u32 },
59    #[error("The received Vec length is longer than maximum allowed ({length} > {max_length})")]
60    VecLengthTooLong { length: u32, max_length: u32 },
61    #[error("{source}")]
62    Io {
63        #[from]
64        #[backtrace]
65        source: io::Error,
66    },
67    #[error("Invalid UTF-8: {bytes:?} (lossy: {lossy:?})")]
68    InvalidUtf8 {
69        bytes: Vec<u8>,
70        lossy: String,
71        // backtrace: Backtrace,
72    },
73    #[error("Unexpected enum variant {id}")]
74    UnexpectedEnumVariant { id: i32 },
75    #[error("Unexpected enum variant {id}")]
76    UnexpectedStringEnumVariant { id: String },
77    #[error("Tried to read {attempted_read} bytes but there were only {actual_read}")]
78    UnexpectedEof {
79        attempted_read: usize,
80        actual_read: usize,
81        backtrace: Backtrace,
82    },
83    #[error("{0}")]
84    Custom(String),
85    #[cfg(feature = "serde_json")]
86    #[error("{source}")]
87    Deserialization {
88        #[from]
89        #[backtrace]
90        source: serde_json::Error,
91    },
92    #[error("{source}")]
93    Nbt {
94        #[from]
95        #[backtrace]
96        source: simdnbt::Error,
97    },
98    #[error("{source}")]
99    DeserializeNbt {
100        #[from]
101        #[backtrace]
102        source: simdnbt::DeserializeError,
103    },
104}
105
106pub(crate) fn read_bytes<'a>(
107    buf: &'a mut Cursor<&[u8]>,
108    length: usize,
109) -> Result<&'a [u8], BufReadError> {
110    if length > (buf.get_ref().len() - buf.position() as usize) {
111        return Err(BufReadError::UnexpectedEof {
112            attempted_read: length,
113            actual_read: buf.get_ref().len() - buf.position() as usize,
114            backtrace: Backtrace::capture(),
115        });
116    }
117    let initial_position = buf.position() as usize;
118    buf.set_position(buf.position() + length as u64);
119    let data = &buf.get_ref()[initial_position..initial_position + length];
120    Ok(data)
121}
122
123pub(crate) fn read_utf_with_len<'a>(
124    buf: &'a mut Cursor<&[u8]>,
125    max_length: u32,
126) -> Result<&'a str, BufReadError> {
127    let length = u32::azalea_read_var(buf)?;
128    // i don't know why it's multiplied by 4 but it's like that in mojang's code so
129    if length > max_length * 4 {
130        return Err(BufReadError::StringLengthTooLong {
131            length,
132            max_length: max_length * 4,
133        });
134    }
135
136    let buffer = read_bytes(buf, length as usize)?;
137    let string = std::str::from_utf8(buffer).map_err(|_| BufReadError::InvalidUtf8 {
138        bytes: buffer.to_vec(),
139        lossy: String::from_utf8_lossy(buffer).to_string(),
140        // backtrace: Backtrace::capture(),
141    })?;
142    if string.len() > length as usize {
143        return Err(BufReadError::StringLengthTooLong { length, max_length });
144    }
145
146    Ok(string)
147}
148
149pub(crate) fn write_utf_with_len(
150    buf: &mut impl Write,
151    string: &str,
152    max_len: u32,
153) -> io::Result<()> {
154    let actual_len = string.len();
155    if actual_len > max_len as usize {
156        panic!("String too big (was {actual_len} bytes encoded, max {max_len})");
157    }
158    string.as_bytes().to_vec().azalea_write(buf)?;
159    Ok(())
160}