Skip to main content

ghast_protocol/
nbt.rs

1//! Named Binary Tag (NBT) as used on the network by 1.8 – 1.20.1.
2//!
3//! The protocol carries NBT as a *named root tag*: a tag-type byte, a `u16`-prefixed
4//! name and the payload. A single `0` byte (TAG_End) means "no compound" — vanilla's
5//! packet buffer reads both its plain and its optional NBT that way.
6//!
7//! Packets keep NBT as [`RawNbt`]: the exact bytes of the root tag, found by *skipping*
8//! over the structure without allocating. Turning it into a tree ([`RawNbt::parse`]) is
9//! opt-in, so a client that only needs item ids and counts from `window_items` never pays
10//! for building NBT trees (on busy servers that NBT is most of the inbound bytes).
11//!
12//! Strings are Java "Modified UTF-8" (NUL as `C0 80`, supplementary characters as
13//! surrogate pairs). Raw bytes are always preserved exactly; the parsed tree decodes
14//! strings lossily (unpaired surrogates become U+FFFD).
15
16use bytes::{BufMut, Bytes, BytesMut};
17
18use crate::codec::{Decode, DecodeResult, Encode, EncodeResult, Reader};
19use crate::error::{DecodeError, EncodeError};
20
21/// Maximum nesting depth of compounds and lists (same as vanilla).
22pub const MAX_DEPTH: usize = 512;
23
24/// NBT tag type ids.
25#[allow(missing_docs)]
26pub mod tag {
27    pub const END: u8 = 0;
28    pub const BYTE: u8 = 1;
29    pub const SHORT: u8 = 2;
30    pub const INT: u8 = 3;
31    pub const LONG: u8 = 4;
32    pub const FLOAT: u8 = 5;
33    pub const DOUBLE: u8 = 6;
34    pub const BYTE_ARRAY: u8 = 7;
35    pub const STRING: u8 = 8;
36    pub const LIST: u8 = 9;
37    pub const COMPOUND: u8 = 10;
38    pub const INT_ARRAY: u8 = 11;
39    pub const LONG_ARRAY: u8 = 12;
40}
41
42/// The undecoded bytes of one network NBT root tag (type byte, name and payload), or a
43/// single `0` byte for "no compound".
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub struct RawNbt(Bytes);
46
47impl RawNbt {
48    /// The "no compound" value (a single TAG_End byte).
49    pub const END: RawNbt = RawNbt(Bytes::from_static(&[0]));
50
51    /// The raw bytes, exactly as they appear on the wire.
52    #[inline]
53    pub fn as_bytes(&self) -> &Bytes {
54        &self.0
55    }
56
57    /// Whether this is the single-byte "no compound" value.
58    #[inline]
59    pub fn is_end(&self) -> bool {
60        self.0.first() == Some(&tag::END)
61    }
62
63    /// Validates `bytes` as exactly one root tag and wraps them.
64    pub fn from_bytes(bytes: Bytes) -> DecodeResult<Self> {
65        let mut r = Reader::new(&bytes);
66        let v = Self::read(&mut r)?;
67        r.finish()?;
68        Ok(v)
69    }
70
71    /// Reads a root tag (or a single TAG_End byte) by skipping over its structure.
72    pub fn read(r: &mut Reader<'_>) -> DecodeResult<Self> {
73        let start = r.position();
74        let ty = r.read_u8()?;
75        if ty != tag::END {
76            let name_len = r.read_u16()? as usize;
77            r.skip(name_len)?;
78            skip_payload(r, ty, 0)?;
79        }
80        Ok(RawNbt(r.bytes_since(start)))
81    }
82
83    /// Reads protodef `optionalNbt`: a `0` byte is `None`, anything else a root tag.
84    pub fn read_optional(r: &mut Reader<'_>) -> DecodeResult<Option<Self>> {
85        if r.peek_u8()? == tag::END {
86            r.skip(1)?;
87            Ok(None)
88        } else {
89            Self::read(r).map(Some)
90        }
91    }
92
93    /// Writes the raw bytes.
94    #[inline]
95    pub fn write(&self, w: &mut BytesMut) {
96        w.put_slice(&self.0);
97    }
98
99    /// Writes protodef `optionalNbt`.
100    #[inline]
101    pub fn write_optional(v: &Option<Self>, w: &mut BytesMut) {
102        match v {
103            Some(n) => n.write(w),
104            None => w.put_u8(tag::END),
105        }
106    }
107
108    /// Parses the tree. Returns `None` for the "no compound" value.
109    pub fn parse(&self) -> DecodeResult<Option<NamedTag>> {
110        let mut r = Reader::new(&self.0);
111        let ty = r.read_u8()?;
112        if ty == tag::END {
113            return Ok(None);
114        }
115        let name = read_mutf8(&mut r)?;
116        let tag = read_payload(&mut r, ty, 0)?;
117        r.finish()?;
118        Ok(Some(NamedTag { name, tag }))
119    }
120
121    /// Serialises a tree into raw network bytes.
122    pub fn from_named(t: &NamedTag) -> Result<Self, EncodeError> {
123        let mut w = BytesMut::new();
124        t.encode(&mut w)?;
125        Ok(RawNbt(w.freeze()))
126    }
127}
128
129impl Decode for RawNbt {
130    #[inline]
131    fn decode(r: &mut Reader<'_>) -> DecodeResult<Self> {
132        Self::read(r)
133    }
134}
135
136impl Encode for RawNbt {
137    #[inline]
138    fn encode(&self, w: &mut BytesMut) -> EncodeResult {
139        self.write(w);
140        Ok(())
141    }
142}
143
144/// Skips the payload of a tag of type `ty`.
145fn skip_payload(r: &mut Reader<'_>, ty: u8, depth: usize) -> DecodeResult<()> {
146    match ty {
147        tag::BYTE => r.skip(1),
148        tag::SHORT => r.skip(2),
149        tag::INT | tag::FLOAT => r.skip(4),
150        tag::LONG | tag::DOUBLE => r.skip(8),
151        tag::BYTE_ARRAY => {
152            let n = read_array_len(r, 1)?;
153            r.skip(n)
154        }
155        tag::INT_ARRAY => {
156            let n = read_array_len(r, 4)?;
157            r.skip(n * 4)
158        }
159        tag::LONG_ARRAY => {
160            let n = read_array_len(r, 8)?;
161            r.skip(n * 8)
162        }
163        tag::STRING => {
164            let n = r.read_u16()? as usize;
165            r.skip(n)
166        }
167        tag::LIST => {
168            if depth >= MAX_DEPTH {
169                return Err(DecodeError::NbtTooDeep(MAX_DEPTH));
170            }
171            let elem = r.read_u8()?;
172            let n = read_list_len(r, elem)?;
173            match elem {
174                // Fixed-size elements: skip in one step.
175                tag::BYTE => r.skip(n),
176                tag::SHORT => r.skip(n * 2),
177                tag::INT | tag::FLOAT => r.skip(n * 4),
178                tag::LONG | tag::DOUBLE => r.skip(n * 8),
179                _ => {
180                    for _ in 0..n {
181                        skip_payload(r, elem, depth + 1)?;
182                    }
183                    Ok(())
184                }
185            }
186        }
187        tag::COMPOUND => {
188            if depth >= MAX_DEPTH {
189                return Err(DecodeError::NbtTooDeep(MAX_DEPTH));
190            }
191            loop {
192                let t = r.read_u8()?;
193                if t == tag::END {
194                    return Ok(());
195                }
196                let name_len = r.read_u16()? as usize;
197                r.skip(name_len)?;
198                skip_payload(r, t, depth + 1)?;
199            }
200        }
201        other => Err(DecodeError::InvalidNbtTag(other)),
202    }
203}
204
205/// Reads an `i32` array length and checks it against the remaining input.
206fn read_array_len(r: &mut Reader<'_>, elem_size: usize) -> DecodeResult<usize> {
207    let n = r.read_i32()?;
208    r.check_count(i64::from(n), elem_size)
209}
210
211/// Smallest encoded size of one list element of type `elem`.
212fn min_size(elem: u8) -> usize {
213    match elem {
214        tag::BYTE | tag::COMPOUND => 1,
215        tag::SHORT | tag::STRING => 2,
216        tag::INT | tag::FLOAT | tag::BYTE_ARRAY | tag::INT_ARRAY | tag::LONG_ARRAY => 4,
217        tag::LONG | tag::DOUBLE => 8,
218        tag::LIST => 5,
219        _ => 1,
220    }
221}
222
223fn read_list_len(r: &mut Reader<'_>, elem: u8) -> DecodeResult<usize> {
224    let n = r.read_i32()?;
225    if elem == tag::END {
226        return if n <= 0 {
227            Ok(0)
228        } else {
229            Err(DecodeError::NbtEndList)
230        };
231    }
232    if elem > tag::LONG_ARRAY {
233        return Err(DecodeError::InvalidNbtTag(elem));
234    }
235    r.check_count(i64::from(n), min_size(elem))
236}
237
238/// A named root tag.
239#[derive(Debug, Clone, PartialEq)]
240pub struct NamedTag {
241    /// Root name (usually empty on the network).
242    pub name: String,
243    /// The root tag (usually a compound).
244    pub tag: Tag,
245}
246
247/// An NBT tag.
248#[derive(Debug, Clone, PartialEq)]
249#[allow(missing_docs)]
250pub enum Tag {
251    Byte(i8),
252    Short(i16),
253    Int(i32),
254    Long(i64),
255    Float(f32),
256    Double(f64),
257    ByteArray(Bytes),
258    String(String),
259    List(List),
260    Compound(Compound),
261    IntArray(Vec<i32>),
262    LongArray(Vec<i64>),
263}
264
265/// An NBT list: element type id and elements (all of that type).
266#[derive(Debug, Clone, PartialEq, Default)]
267pub struct List {
268    /// Element tag type ([`tag::END`] for an empty untyped list).
269    pub elem_type: u8,
270    /// The elements; each must be of `elem_type`.
271    pub items: Vec<Tag>,
272}
273
274/// An NBT compound: named tags in wire order.
275#[derive(Debug, Clone, PartialEq, Default)]
276pub struct Compound(pub Vec<(String, Tag)>);
277
278impl Compound {
279    /// Looks up an entry by name (linear scan; compounds are small).
280    pub fn get(&self, name: &str) -> Option<&Tag> {
281        self.0.iter().find(|(k, _)| k == name).map(|(_, v)| v)
282    }
283}
284
285impl Tag {
286    /// The tag type id.
287    pub fn type_id(&self) -> u8 {
288        match self {
289            Tag::Byte(_) => tag::BYTE,
290            Tag::Short(_) => tag::SHORT,
291            Tag::Int(_) => tag::INT,
292            Tag::Long(_) => tag::LONG,
293            Tag::Float(_) => tag::FLOAT,
294            Tag::Double(_) => tag::DOUBLE,
295            Tag::ByteArray(_) => tag::BYTE_ARRAY,
296            Tag::String(_) => tag::STRING,
297            Tag::List(_) => tag::LIST,
298            Tag::Compound(_) => tag::COMPOUND,
299            Tag::IntArray(_) => tag::INT_ARRAY,
300            Tag::LongArray(_) => tag::LONG_ARRAY,
301        }
302    }
303
304    /// The compound, if this is one.
305    pub fn as_compound(&self) -> Option<&Compound> {
306        match self {
307            Tag::Compound(c) => Some(c),
308            _ => None,
309        }
310    }
311}
312
313fn read_payload(r: &mut Reader<'_>, ty: u8, depth: usize) -> DecodeResult<Tag> {
314    Ok(match ty {
315        tag::BYTE => Tag::Byte(r.read_i8()?),
316        tag::SHORT => Tag::Short(r.read_i16()?),
317        tag::INT => Tag::Int(r.read_i32()?),
318        tag::LONG => Tag::Long(r.read_i64()?),
319        tag::FLOAT => Tag::Float(r.read_f32()?),
320        tag::DOUBLE => Tag::Double(r.read_f64()?),
321        tag::BYTE_ARRAY => {
322            let n = read_array_len(r, 1)?;
323            Tag::ByteArray(r.read_bytes(n)?)
324        }
325        tag::INT_ARRAY => {
326            let n = read_array_len(r, 4)?;
327            let s = r.take(n * 4)?;
328            Tag::IntArray(
329                s.as_chunks::<4>()
330                    .0
331                    .iter()
332                    .map(|c| i32::from_be_bytes(*c))
333                    .collect(),
334            )
335        }
336        tag::LONG_ARRAY => {
337            let n = read_array_len(r, 8)?;
338            let s = r.take(n * 8)?;
339            Tag::LongArray(
340                s.as_chunks::<8>()
341                    .0
342                    .iter()
343                    .map(|c| i64::from_be_bytes(*c))
344                    .collect(),
345            )
346        }
347        tag::STRING => Tag::String(read_mutf8(r)?),
348        tag::LIST => {
349            if depth >= MAX_DEPTH {
350                return Err(DecodeError::NbtTooDeep(MAX_DEPTH));
351            }
352            let elem = r.read_u8()?;
353            let n = read_list_len(r, elem)?;
354            let mut items = Vec::with_capacity(n);
355            for _ in 0..n {
356                items.push(read_payload(r, elem, depth + 1)?);
357            }
358            Tag::List(List {
359                elem_type: elem,
360                items,
361            })
362        }
363        tag::COMPOUND => {
364            if depth >= MAX_DEPTH {
365                return Err(DecodeError::NbtTooDeep(MAX_DEPTH));
366            }
367            let mut entries = Vec::new();
368            loop {
369                let t = r.read_u8()?;
370                if t == tag::END {
371                    break;
372                }
373                let name = read_mutf8(r)?;
374                entries.push((name, read_payload(r, t, depth + 1)?));
375            }
376            Tag::Compound(Compound(entries))
377        }
378        other => return Err(DecodeError::InvalidNbtTag(other)),
379    })
380}
381
382impl Encode for NamedTag {
383    fn encode(&self, w: &mut BytesMut) -> EncodeResult {
384        w.put_u8(self.tag.type_id());
385        write_mutf8(w, &self.name)?;
386        write_payload(w, &self.tag, 0)
387    }
388}
389
390fn i32_len(n: usize) -> Result<i32, EncodeError> {
391    i32::try_from(n).map_err(|_| EncodeError::LengthOverflow {
392        len: n,
393        max: i32::MAX as usize,
394    })
395}
396
397fn write_payload(w: &mut BytesMut, t: &Tag, depth: usize) -> EncodeResult {
398    if depth > MAX_DEPTH {
399        return Err(EncodeError::Invalid("NBT nesting too deep"));
400    }
401    match t {
402        Tag::Byte(v) => w.put_i8(*v),
403        Tag::Short(v) => w.put_i16(*v),
404        Tag::Int(v) => w.put_i32(*v),
405        Tag::Long(v) => w.put_i64(*v),
406        Tag::Float(v) => w.put_f32(*v),
407        Tag::Double(v) => w.put_f64(*v),
408        Tag::ByteArray(b) => {
409            w.put_i32(i32_len(b.len())?);
410            w.put_slice(b);
411        }
412        Tag::String(s) => write_mutf8(w, s)?,
413        Tag::List(l) => {
414            w.put_u8(l.elem_type);
415            w.put_i32(i32_len(l.items.len())?);
416            if l.elem_type == tag::END && !l.items.is_empty() {
417                return Err(EncodeError::Invalid("NBT list of TAG_End with elements"));
418            }
419            for item in &l.items {
420                if item.type_id() != l.elem_type {
421                    return Err(EncodeError::Invalid("NBT list element of the wrong type"));
422                }
423                write_payload(w, item, depth + 1)?;
424            }
425        }
426        Tag::Compound(c) => {
427            for (k, v) in &c.0 {
428                w.put_u8(v.type_id());
429                write_mutf8(w, k)?;
430                write_payload(w, v, depth + 1)?;
431            }
432            w.put_u8(tag::END);
433        }
434        Tag::IntArray(a) => {
435            w.put_i32(i32_len(a.len())?);
436            for v in a {
437                w.put_i32(*v);
438            }
439        }
440        Tag::LongArray(a) => {
441            w.put_i32(i32_len(a.len())?);
442            for v in a {
443                w.put_i64(*v);
444            }
445        }
446    }
447    Ok(())
448}
449
450/// Reads a `u16`-prefixed Modified UTF-8 string.
451fn read_mutf8(r: &mut Reader<'_>) -> DecodeResult<String> {
452    let n = r.read_u16()? as usize;
453    let b = r.take(n)?;
454    decode_mutf8(b)
455}
456
457/// Decodes Java Modified UTF-8.
458pub fn decode_mutf8(b: &[u8]) -> DecodeResult<String> {
459    // Fast path: plain ASCII without NUL (by far the common case) or UTF-8 that cannot
460    // contain MUTF-8 specials (`C0 80` for NUL, `ED` lead bytes for surrogates).
461    if b.iter().all(|&c| c != 0 && c < 0x80) {
462        return std::str::from_utf8(b)
463            .map(str::to_owned)
464            .map_err(|_| DecodeError::InvalidUtf8);
465    }
466    if !b.iter().any(|&c| c == 0 || c == 0xc0 || c == 0xed)
467        && let Ok(s) = std::str::from_utf8(b)
468    {
469        return Ok(s.to_owned());
470    }
471    let mut units: Vec<u16> = Vec::with_capacity(b.len());
472    let mut i = 0;
473    while i < b.len() {
474        let c = b[i];
475        if c < 0x80 {
476            units.push(u16::from(c));
477            i += 1;
478        } else if c & 0xe0 == 0xc0 {
479            let c2 = *b.get(i + 1).ok_or(DecodeError::InvalidUtf8)?;
480            if c2 & 0xc0 != 0x80 {
481                return Err(DecodeError::InvalidUtf8);
482            }
483            units.push((u16::from(c & 0x1f) << 6) | u16::from(c2 & 0x3f));
484            i += 2;
485        } else if c & 0xf0 == 0xe0 {
486            let c2 = *b.get(i + 1).ok_or(DecodeError::InvalidUtf8)?;
487            let c3 = *b.get(i + 2).ok_or(DecodeError::InvalidUtf8)?;
488            if c2 & 0xc0 != 0x80 || c3 & 0xc0 != 0x80 {
489                return Err(DecodeError::InvalidUtf8);
490            }
491            units.push(
492                (u16::from(c & 0x0f) << 12) | (u16::from(c2 & 0x3f) << 6) | u16::from(c3 & 0x3f),
493            );
494            i += 3;
495        } else {
496            return Err(DecodeError::InvalidUtf8);
497        }
498    }
499    Ok(String::from_utf16_lossy(&units))
500}
501
502/// Writes a `u16`-prefixed Modified UTF-8 string.
503fn write_mutf8(w: &mut BytesMut, s: &str) -> EncodeResult {
504    let too_long = |len| EncodeError::LengthOverflow {
505        len,
506        max: u16::MAX as usize,
507    };
508    // Fast path: no NUL and no supplementary characters means MUTF-8 == UTF-8.
509    if s.bytes().all(|b| b != 0 && b < 0xf0) {
510        let n = u16::try_from(s.len()).map_err(|_| too_long(s.len()))?;
511        w.put_u16(n);
512        w.put_slice(s.as_bytes());
513        return Ok(());
514    }
515    let mut out = Vec::with_capacity(s.len() + 8);
516    for u in s.encode_utf16() {
517        match u {
518            0x0001..=0x007f => out.push(u as u8),
519            0x0000 | 0x0080..=0x07ff => {
520                out.push(0xc0 | (u >> 6) as u8);
521                out.push(0x80 | (u & 0x3f) as u8);
522            }
523            _ => {
524                out.push(0xe0 | (u >> 12) as u8);
525                out.push(0x80 | ((u >> 6) & 0x3f) as u8);
526                out.push(0x80 | (u & 0x3f) as u8);
527            }
528        }
529    }
530    let n = u16::try_from(out.len()).map_err(|_| too_long(out.len()))?;
531    w.put_u16(n);
532    w.put_slice(&out);
533    Ok(())
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    /// The classic `hello_world.nbt` test file (uncompressed).
541    const HELLO_WORLD: &[u8] = &[
542        0x0a, 0x00, 0x0b, b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', 0x08,
543        0x00, 0x04, b'n', b'a', b'm', b'e', 0x00, 0x09, b'B', b'a', b'n', b'a', b'n', b'r', b'a',
544        b'm', b'a', 0x00,
545    ];
546
547    #[test]
548    fn hello_world() {
549        let b = Bytes::from_static(HELLO_WORLD);
550        let raw = RawNbt::from_bytes(b.clone()).unwrap();
551        let t = raw.parse().unwrap().unwrap();
552        assert_eq!(t.name, "hello world");
553        let c = t.tag.as_compound().unwrap();
554        assert_eq!(c.get("name"), Some(&Tag::String("Bananrama".into())));
555        assert_eq!(RawNbt::from_named(&t).unwrap().as_bytes(), &b);
556    }
557
558    #[test]
559    fn optional_and_end() {
560        let b = Bytes::from_static(&[0x00, 0x01]);
561        let mut r = Reader::new(&b);
562        assert_eq!(RawNbt::read_optional(&mut r), Ok(None));
563        assert_eq!(r.remaining(), 1);
564        let b = Bytes::from_static(&[0x00]);
565        let raw = RawNbt::from_bytes(b).unwrap();
566        assert!(raw.is_end());
567        assert_eq!(raw.parse(), Ok(None));
568    }
569
570    #[test]
571    fn rejects_bad_input() {
572        // Unknown tag type inside a compound.
573        let b = Bytes::from_static(&[0x0a, 0x00, 0x00, 0x0d, 0x00, 0x00]);
574        assert_eq!(RawNbt::from_bytes(b), Err(DecodeError::InvalidNbtTag(13)));
575        // List of TAG_End with a positive length.
576        let b = Bytes::from_static(&[0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05]);
577        assert_eq!(RawNbt::from_bytes(b), Err(DecodeError::NbtEndList));
578        // Huge int array length is bounded by the input instead of allocating.
579        let b = Bytes::from_static(&[0x0b, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff]);
580        assert!(matches!(
581            RawNbt::from_bytes(b),
582            Err(DecodeError::LengthTooLarge { .. })
583        ));
584        // Deep nesting is limited.
585        let mut v = vec![0x09, 0x00, 0x00];
586        for _ in 0..600 {
587            v.extend_from_slice(&[0x09, 0x00, 0x00, 0x00, 0x01]);
588        }
589        let b = Bytes::from(v);
590        assert_eq!(
591            RawNbt::from_bytes(b),
592            Err(DecodeError::NbtTooDeep(MAX_DEPTH))
593        );
594    }
595
596    #[test]
597    fn modified_utf8() {
598        let s = "a\0b\u{e9}\u{1f600}";
599        let mut w = BytesMut::new();
600        write_mutf8(&mut w, s).unwrap();
601        // NUL -> C0 80, U+1F600 -> surrogate pair, each 3 bytes.
602        assert_eq!(
603            &w[..],
604            &[
605                0x00, 0x0c, b'a', 0xc0, 0x80, b'b', 0xc3, 0xa9, 0xed, 0xa0, 0xbd, 0xed, 0xb8, 0x80
606            ]
607        );
608        assert_eq!(decode_mutf8(&w[2..]).unwrap(), s);
609        assert_eq!(decode_mutf8(&[0xc3]), Err(DecodeError::InvalidUtf8));
610        // Standard 4-byte UTF-8 (as written by some non-Java encoders) is accepted.
611        assert_eq!(
612            decode_mutf8(&[0xf0, 0x9f, 0x98, 0x80]).unwrap(),
613            "\u{1f600}"
614        );
615        assert_eq!(decode_mutf8(&[0xf8, 0x80]), Err(DecodeError::InvalidUtf8));
616    }
617}