Skip to main content

deepslate_protocol/
codec.rs

1//! Packet framing, compression, and high-level encode/decode functions.
2//!
3//! The Minecraft wire format is:
4//! - Without compression: `[VarInt: packet_length][VarInt: packet_id][payload]`
5//! - With compression (below threshold): `[VarInt: packet_length][0x00][VarInt: packet_id][payload]`
6//! - With compression (above threshold): `[VarInt: packet_length][VarInt: uncompressed_size][zlib(packet_id + payload)]`
7
8use bytes::{Buf, BufMut};
9
10use crate::types::ProtocolError;
11use crate::varint;
12
13/// Maximum allowed frame size (2 MiB). Frames larger than this are rejected
14/// to prevent memory exhaustion attacks.
15const MAX_FRAME_SIZE: usize = 2 * 1024 * 1024;
16
17/// Maximum allowed uncompressed packet size (8 MiB).
18pub const MAX_UNCOMPRESSED_SIZE: usize = 8 * 1024 * 1024;
19
20/// Try to extract a single frame from the given buffer.
21///
22/// Returns `Ok(Some((varint_size, frame_len)))` if a complete frame is
23/// available, `Ok(None)` if more data is needed, or `Err` if the frame is
24/// malformed.
25///
26/// The frame body starts at offset `varint_size` and spans `frame_len` bytes.
27/// The total bytes consumed is `varint_size + frame_len`. This function does
28/// **not** copy or allocate — callers should use `BytesMut::split_to()` or
29/// equivalent zero-copy operations to extract the frame data.
30///
31/// # Errors
32///
33/// Returns `ProtocolError::FrameTooLarge` if the frame exceeds `MAX_FRAME_SIZE`.
34/// Returns `ProtocolError::VarIntTooLong` if the length prefix is malformed.
35#[allow(clippy::cast_sign_loss)]
36pub fn try_read_frame(data: &[u8]) -> Result<Option<(usize, usize)>, ProtocolError> {
37    let Some((frame_len, varint_size)) = varint::peek_var_int(data)? else {
38        return Ok(None);
39    };
40
41    if frame_len < 0 {
42        return Err(ProtocolError::InvalidData(
43            "negative frame length".to_string(),
44        ));
45    }
46
47    let frame_len = frame_len as usize;
48    if frame_len > MAX_FRAME_SIZE {
49        return Err(ProtocolError::FrameTooLarge {
50            size: frame_len,
51            max: MAX_FRAME_SIZE,
52        });
53    }
54
55    let total = varint_size + frame_len;
56    if data.len() < total {
57        return Ok(None);
58    }
59
60    Ok(Some((varint_size, frame_len)))
61}
62
63/// Write a frame (length-prefixed) into the destination buffer.
64/// `inner` is the already-encoded packet data (packet ID + payload).
65#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
66pub fn write_frame(dst: &mut impl BufMut, inner: &[u8]) {
67    varint::write_var_int(dst, inner.len() as i32);
68    dst.put_slice(inner);
69}
70
71/// Wrap packet data with the compression format.
72///
73/// If `data_len >= threshold`, the data should already be zlib-compressed and
74/// `uncompressed_size` should be the original size. If below threshold, pass
75/// `uncompressed_size = 0` and provide the raw data.
76#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
77pub fn write_compressed_frame(dst: &mut impl BufMut, uncompressed_size: i32, payload: &[u8]) {
78    let size_varint_len = varint::var_int_bytes(uncompressed_size);
79    let frame_len = size_varint_len + payload.len();
80    varint::write_var_int(dst, frame_len as i32);
81    varint::write_var_int(dst, uncompressed_size);
82    dst.put_slice(payload);
83}
84
85/// Decode the inner frame data when compression is enabled.
86///
87/// Returns `(uncompressed_size, payload)` where:
88/// - `uncompressed_size == 0` means the payload is NOT compressed
89/// - `uncompressed_size > 0` means the payload is zlib-compressed
90///
91/// The returned payload is a sub-slice of the input — no allocation or copy.
92///
93/// # Errors
94///
95/// Returns errors for malformed data.
96#[allow(clippy::cast_sign_loss)]
97pub fn read_compressed_frame(data: &[u8]) -> Result<(usize, &[u8]), ProtocolError> {
98    let mut cursor = data;
99    let uncompressed_size = varint::read_var_int(&mut cursor)?;
100    if uncompressed_size < 0 {
101        return Err(ProtocolError::InvalidData(
102            "negative uncompressed size".to_string(),
103        ));
104    }
105    let uncompressed_size = uncompressed_size as usize;
106    if uncompressed_size > MAX_UNCOMPRESSED_SIZE {
107        return Err(ProtocolError::CompressionError(format!(
108            "uncompressed size {uncompressed_size} exceeds maximum {MAX_UNCOMPRESSED_SIZE}"
109        )));
110    }
111    Ok((uncompressed_size, cursor))
112}
113
114/// Encode a typed packet into raw bytes (packet ID + fields), without framing.
115pub fn encode_packet_data(packet_id: i32, encode_fn: impl FnOnce(&mut Vec<u8>)) -> Vec<u8> {
116    let mut buf = Vec::with_capacity(64);
117    varint::write_var_int(&mut buf, packet_id);
118    encode_fn(&mut buf);
119    buf
120}
121
122/// Read a packet ID from frame data.
123///
124/// # Errors
125///
126/// Returns `ProtocolError` if the packet ID `VarInt` is malformed.
127pub fn read_packet_id(buf: &mut impl Buf) -> Result<i32, ProtocolError> {
128    varint::read_var_int(buf)
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_try_read_frame_complete() {
137        // Frame: length=3, data=[0x01, 0x02, 0x03]
138        let data = vec![0x03, 0x01, 0x02, 0x03];
139        let (varint_size, frame_len) = try_read_frame(&data).unwrap().unwrap();
140        assert_eq!(varint_size + frame_len, 4);
141        assert_eq!(
142            &data[varint_size..varint_size + frame_len],
143            &[0x01, 0x02, 0x03]
144        );
145    }
146
147    #[test]
148    fn test_try_read_frame_incomplete() {
149        let data = vec![0x03, 0x01]; // Says 3 bytes but only 1 available
150        assert!(try_read_frame(&data).unwrap().is_none());
151    }
152
153    #[test]
154    fn test_try_read_frame_empty() {
155        assert!(try_read_frame(&[]).unwrap().is_none());
156    }
157
158    #[test]
159    fn test_write_frame_roundtrip() {
160        let inner = vec![0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F]; // packet_id=0, "Hello"
161        let mut buf = Vec::new();
162        write_frame(&mut buf, &inner);
163        let (varint_size, frame_len) = try_read_frame(&buf).unwrap().unwrap();
164        assert_eq!(varint_size + frame_len, buf.len());
165        assert_eq!(&buf[varint_size..varint_size + frame_len], &inner[..]);
166    }
167
168    #[test]
169    fn test_compressed_frame_uncompressed() {
170        let mut buf = Vec::new();
171        let payload = vec![0x00, 0x48, 0x69];
172        write_compressed_frame(&mut buf, 0, &payload);
173
174        let (varint_size, frame_len) = try_read_frame(&buf).unwrap().unwrap();
175        let frame_data = &buf[varint_size..varint_size + frame_len];
176        let (uncompressed_size, data) = read_compressed_frame(frame_data).unwrap();
177        assert_eq!(uncompressed_size, 0);
178        assert_eq!(data, &payload[..]);
179    }
180}