azalea_buf/impls/
mod.rs

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