Skip to main content

basalt_types/
primitives.rs

1use crate::{Decode, Encode, EncodedSize, Error, Result};
2
3/// Encodes a boolean as a single byte in the Minecraft protocol.
4///
5/// The Minecraft protocol represents booleans as a single unsigned byte:
6/// `0x00` for `false`, `0x01` for `true`. This is used in many packets
7/// for flags like on-ground state, sneaking, sprinting, etc.
8impl Encode for bool {
9    /// Writes `0x01` if true, `0x00` if false. Always writes exactly one byte.
10    fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
11        buf.push(if *self { 0x01 } else { 0x00 });
12        Ok(())
13    }
14}
15
16/// Decodes a boolean from a single byte in the Minecraft protocol.
17///
18/// Any non-zero byte is interpreted as `true`, matching the Minecraft
19/// server behavior. This is intentionally lenient — the protocol spec
20/// says `0x01` for true, but servers may send other non-zero values.
21impl Decode for bool {
22    /// Reads one byte. Returns `true` for any non-zero value, `false` for `0x00`.
23    ///
24    /// Fails with `BufferUnderflow` if the buffer is empty.
25    fn decode(buf: &mut &[u8]) -> Result<Self> {
26        if buf.is_empty() {
27            return Err(Error::BufferUnderflow {
28                needed: 1,
29                available: 0,
30            });
31        }
32        let value = buf[0] != 0;
33        *buf = &buf[1..];
34        Ok(value)
35    }
36}
37
38/// A boolean always occupies exactly one byte on the wire.
39impl EncodedSize for bool {
40    fn encoded_size(&self) -> usize {
41        1
42    }
43}
44
45/// Generates `Encode`, `Decode`, and `EncodedSize` implementations for
46/// fixed-size numeric types using big-endian byte order.
47///
48/// The Minecraft protocol uses big-endian (network byte order) for all
49/// fixed-size integers and floating-point values. Each type occupies a
50/// fixed number of bytes on the wire, regardless of the value.
51macro_rules! impl_numeric {
52    ($ty:ty, $size:expr) => {
53        /// Encodes as a fixed-size big-endian value.
54        ///
55        /// The Minecraft protocol uses big-endian (network byte order) for all
56        /// fixed-size numeric types. The value is written as exactly
57        #[doc = concat!(stringify!($size), " bytes.")]
58        impl Encode for $ty {
59            /// Writes the value as big-endian bytes. Always writes exactly
60            #[doc = concat!(stringify!($size), " bytes.")]
61            fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
62                buf.extend_from_slice(&self.to_be_bytes());
63                Ok(())
64            }
65        }
66
67        /// Decodes from a fixed-size big-endian value.
68        ///
69        /// Reads exactly
70        #[doc = concat!(stringify!($size), " bytes from the buffer and interprets them as big-endian.")]
71        impl Decode for $ty {
72            /// Reads
73            #[doc = concat!(stringify!($size), " big-endian bytes and advances the cursor.")]
74            ///
75            /// Fails with `BufferUnderflow` if fewer than
76            #[doc = concat!(stringify!($size), " bytes remain.")]
77            fn decode(buf: &mut &[u8]) -> Result<Self> {
78                if buf.len() < $size {
79                    return Err(Error::BufferUnderflow {
80                        needed: $size,
81                        available: buf.len(),
82                    });
83                }
84                let (bytes, rest) = buf.split_at($size);
85                let value = <$ty>::from_be_bytes(bytes.try_into().unwrap());
86                *buf = rest;
87                Ok(value)
88            }
89        }
90
91        /// The encoded size is always
92        #[doc = concat!(stringify!($size), " bytes, regardless of the value.")]
93        impl EncodedSize for $ty {
94            fn encoded_size(&self) -> usize {
95                $size
96            }
97        }
98    };
99}
100
101impl_numeric!(u8, 1);
102impl_numeric!(u16, 2);
103impl_numeric!(u32, 4);
104impl_numeric!(u64, 8);
105impl_numeric!(i8, 1);
106impl_numeric!(i16, 2);
107impl_numeric!(i32, 4);
108impl_numeric!(i64, 8);
109impl_numeric!(f32, 4);
110impl_numeric!(f64, 8);
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    /// Helper: encode a value, then decode it and verify roundtrip.
117    fn roundtrip<T: Encode + Decode + EncodedSize + PartialEq + std::fmt::Debug>(value: T) {
118        let mut buf = Vec::with_capacity(value.encoded_size());
119        value.encode(&mut buf).unwrap();
120        assert_eq!(buf.len(), value.encoded_size());
121
122        let mut cursor = buf.as_slice();
123        let decoded = T::decode(&mut cursor).unwrap();
124        assert!(cursor.is_empty());
125        assert_eq!(decoded, value);
126    }
127
128    /// Helper: verify decode fails on a too-short buffer.
129    fn decode_underflow<T: Decode + std::fmt::Debug>(short_buf: &[u8]) {
130        let mut cursor = short_buf;
131        let result = T::decode(&mut cursor);
132        assert!(matches!(result, Err(Error::BufferUnderflow { .. })));
133    }
134
135    // -- bool --
136
137    #[test]
138    fn bool_true() {
139        roundtrip(true);
140    }
141
142    #[test]
143    fn bool_false() {
144        roundtrip(false);
145    }
146
147    #[test]
148    fn bool_nonzero_is_true() {
149        let mut cursor: &[u8] = &[0x42];
150        assert!(bool::decode(&mut cursor).unwrap());
151    }
152
153    #[test]
154    fn bool_underflow() {
155        decode_underflow::<bool>(&[]);
156    }
157
158    // -- u8 / i8 --
159
160    #[test]
161    fn u8_roundtrip() {
162        roundtrip(0u8);
163        roundtrip(u8::MAX);
164    }
165
166    #[test]
167    fn i8_roundtrip() {
168        roundtrip(0i8);
169        roundtrip(i8::MAX);
170        roundtrip(i8::MIN);
171    }
172
173    // -- u16 / i16 --
174
175    #[test]
176    fn u16_roundtrip() {
177        roundtrip(0u16);
178        roundtrip(u16::MAX);
179    }
180
181    #[test]
182    fn u16_big_endian() {
183        let mut buf = Vec::new();
184        0x0102u16.encode(&mut buf).unwrap();
185        assert_eq!(buf, [0x01, 0x02]);
186    }
187
188    #[test]
189    fn i16_roundtrip() {
190        roundtrip(0i16);
191        roundtrip(i16::MAX);
192        roundtrip(i16::MIN);
193    }
194
195    #[test]
196    fn u16_underflow() {
197        decode_underflow::<u16>(&[0x01]);
198    }
199
200    // -- u32 / i32 --
201
202    #[test]
203    fn u32_roundtrip() {
204        roundtrip(0u32);
205        roundtrip(u32::MAX);
206    }
207
208    #[test]
209    fn u32_big_endian() {
210        let mut buf = Vec::new();
211        0x01020304u32.encode(&mut buf).unwrap();
212        assert_eq!(buf, [0x01, 0x02, 0x03, 0x04]);
213    }
214
215    #[test]
216    fn i32_roundtrip() {
217        roundtrip(0i32);
218        roundtrip(i32::MAX);
219        roundtrip(i32::MIN);
220    }
221
222    #[test]
223    fn u32_underflow() {
224        decode_underflow::<u32>(&[0x01, 0x02, 0x03]);
225    }
226
227    // -- u64 / i64 --
228
229    #[test]
230    fn u64_roundtrip() {
231        roundtrip(0u64);
232        roundtrip(u64::MAX);
233    }
234
235    #[test]
236    fn i64_roundtrip() {
237        roundtrip(0i64);
238        roundtrip(i64::MAX);
239        roundtrip(i64::MIN);
240    }
241
242    #[test]
243    fn u64_underflow() {
244        decode_underflow::<u64>(&[0x01; 7]);
245    }
246
247    // -- f32 / f64 --
248
249    #[test]
250    fn f32_roundtrip() {
251        roundtrip(0.0f32);
252        roundtrip(f32::MAX);
253        roundtrip(f32::MIN);
254        roundtrip(f32::INFINITY);
255        roundtrip(f32::NEG_INFINITY);
256    }
257
258    #[test]
259    fn f32_nan() {
260        let mut buf = Vec::new();
261        f32::NAN.encode(&mut buf).unwrap();
262        let mut cursor = buf.as_slice();
263        let decoded = f32::decode(&mut cursor).unwrap();
264        assert!(decoded.is_nan());
265    }
266
267    #[test]
268    fn f64_roundtrip() {
269        roundtrip(0.0f64);
270        roundtrip(f64::MAX);
271        roundtrip(f64::MIN);
272        roundtrip(f64::INFINITY);
273        roundtrip(f64::NEG_INFINITY);
274    }
275
276    #[test]
277    fn f64_nan() {
278        let mut buf = Vec::new();
279        f64::NAN.encode(&mut buf).unwrap();
280        let mut cursor = buf.as_slice();
281        let decoded = f64::decode(&mut cursor).unwrap();
282        assert!(decoded.is_nan());
283    }
284
285    #[test]
286    fn f64_underflow() {
287        decode_underflow::<f64>(&[0x01; 7]);
288    }
289
290    // -- proptest --
291
292    mod proptests {
293        use super::*;
294        use proptest::prelude::*;
295
296        proptest! {
297            #[test]
298            fn bool_roundtrip(v: bool) {
299                roundtrip(v);
300            }
301
302            #[test]
303            fn u8_roundtrip(v: u8) {
304                roundtrip(v);
305            }
306
307            #[test]
308            fn i8_roundtrip(v: i8) {
309                roundtrip(v);
310            }
311
312            #[test]
313            fn u16_roundtrip(v: u16) {
314                roundtrip(v);
315            }
316
317            #[test]
318            fn i16_roundtrip(v: i16) {
319                roundtrip(v);
320            }
321
322            #[test]
323            fn u32_roundtrip(v: u32) {
324                roundtrip(v);
325            }
326
327            #[test]
328            fn i32_roundtrip(v: i32) {
329                roundtrip(v);
330            }
331
332            #[test]
333            fn u64_roundtrip(v: u64) {
334                roundtrip(v);
335            }
336
337            #[test]
338            fn i64_roundtrip(v: i64) {
339                roundtrip(v);
340            }
341
342            #[test]
343            fn f32_roundtrip_finite(v in proptest::num::f32::NORMAL | proptest::num::f32::SUBNORMAL | proptest::num::f32::ZERO) {
344                roundtrip(v);
345            }
346
347            #[test]
348            fn f64_roundtrip_finite(v in proptest::num::f64::NORMAL | proptest::num::f64::SUBNORMAL | proptest::num::f64::ZERO) {
349                roundtrip(v);
350            }
351        }
352    }
353}