Skip to main content

nbt_rust/
root.rs

1use std::io::{Cursor, Read, Write};
2
3use crate::config::{NbtReadConfig, ParseMode};
4use crate::core::{read_payload_with_config, write_payload};
5use crate::encoding::Encoding;
6use crate::error::{Error, Result};
7use crate::limits::NbtLimits;
8use crate::tag::{Tag, TagType};
9
10fn attach_context<T>(
11    op: &'static str,
12    offset: usize,
13    field: Option<&'static str>,
14    result: Result<T>,
15) -> Result<T> {
16    result.map_err(|error| error.with_context(op, offset, field))
17}
18
19pub const BEDROCK_FILE_HEADER_MAGIC: u32 = 8;
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct RootTag {
23    pub name: String,
24    pub payload: Tag,
25}
26
27impl RootTag {
28    pub fn new(name: impl Into<String>, payload: Tag) -> Self {
29        Self {
30            name: name.into(),
31            payload,
32        }
33    }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum HeaderReadMode {
38    NoHeader,
39    BedrockFileHeader,
40    LevelDatHeader,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum HeaderWriteMode {
45    NoHeader,
46    BedrockFileHeader,
47    LevelDatHeader { storage_version: u32 },
48}
49
50pub fn read_tag<E: Encoding, R: Read>(reader: &mut R) -> Result<RootTag> {
51    read_tag_with_limits::<E, _>(reader, &NbtLimits::default())
52}
53
54pub fn read_tag_with_limits<E: Encoding, R: Read>(
55    reader: &mut R,
56    limits: &NbtLimits,
57) -> Result<RootTag> {
58    read_tag_with_config::<E, _>(reader, &NbtReadConfig::strict(*limits))
59}
60
61pub fn read_tag_with_config<E: Encoding, R: Read>(
62    reader: &mut R,
63    config: &NbtReadConfig,
64) -> Result<RootTag> {
65    let mut id = [0u8; 1];
66    attach_context(
67        "read_exact",
68        0,
69        Some("root_tag_type"),
70        reader.read_exact(&mut id).map_err(Error::from),
71    )?;
72    let tag_type = attach_context(
73        "decode_tag_type",
74        0,
75        Some("root_tag_type"),
76        TagType::try_from(id[0]),
77    )?;
78    if !is_valid_root_tag_type(tag_type, config.parse_mode) {
79        return Err(Error::InvalidRoot { id: id[0] }.with_context(
80            "validate_root_tag_type",
81            0,
82            Some("root_tag_type"),
83        ));
84    }
85
86    let name = read_string::<E, _>(reader, "root_name", &config.limits, 1)?;
87    let payload = read_payload_with_config::<E, _>(reader, tag_type, config)
88        .map_err(|error| error.with_context("read_root_payload", 1, Some("root_payload")))?;
89    Ok(RootTag { name, payload })
90}
91
92pub fn write_tag<E: Encoding, W: Write>(writer: &mut W, root: &RootTag) -> Result<()> {
93    let tag_type = root.payload.tag_type();
94    if !is_valid_root_tag_type(tag_type, ParseMode::Strict) {
95        return Err(Error::InvalidRoot { id: tag_type.id() }.with_context(
96            "validate_root_tag_type",
97            0,
98            Some("root_tag_type"),
99        ));
100    }
101    writer.write_all(&[tag_type.id()])?;
102    write_string::<E, _>(writer, &root.name)?;
103    write_payload::<E, _>(writer, &root.payload)
104}
105
106pub fn read_with_header_mode<E: Encoding, R: Read>(
107    reader: &mut R,
108    mode: HeaderReadMode,
109) -> Result<RootTag> {
110    read_with_header_mode_with_limits::<E, _>(reader, mode, &NbtLimits::default())
111}
112
113pub fn read_with_header_mode_with_limits<E: Encoding, R: Read>(
114    reader: &mut R,
115    mode: HeaderReadMode,
116    limits: &NbtLimits,
117) -> Result<RootTag> {
118    read_with_header_mode_with_config::<E, _>(reader, mode, &NbtReadConfig::strict(*limits))
119}
120
121pub fn read_with_header_mode_with_config<E: Encoding, R: Read>(
122    reader: &mut R,
123    mode: HeaderReadMode,
124    config: &NbtReadConfig,
125) -> Result<RootTag> {
126    match mode {
127        HeaderReadMode::NoHeader => read_tag_with_config::<E, _>(reader, config),
128        HeaderReadMode::BedrockFileHeader => {
129            let magic = read_u32_le(reader, 0, "bedrock_header_magic")?;
130            if magic != BEDROCK_FILE_HEADER_MAGIC {
131                return Err(Error::InvalidHeader {
132                    detail: "bedrock_header_magic_mismatch",
133                    expected: Some(BEDROCK_FILE_HEADER_MAGIC),
134                    actual: Some(magic),
135                }
136                .with_context(
137                    "validate_header_magic",
138                    0,
139                    Some("bedrock_header_magic"),
140                ));
141            }
142            let payload_len = read_u32_le(reader, 4, "bedrock_header_payload_length")? as usize;
143            attach_context(
144                "validate_size",
145                4,
146                Some("header_payload_length"),
147                ensure_within_limit(
148                    "header_payload_length",
149                    payload_len,
150                    config.limits.max_read_bytes,
151                ),
152            )?;
153            read_root_from_len_prefixed_payload::<E, _>(reader, payload_len, config)
154        }
155        HeaderReadMode::LevelDatHeader => {
156            let _storage_version = read_u32_le(reader, 0, "leveldat_storage_version")?;
157            let payload_len = read_u32_le(reader, 4, "leveldat_payload_length")? as usize;
158            attach_context(
159                "validate_size",
160                4,
161                Some("header_payload_length"),
162                ensure_within_limit(
163                    "header_payload_length",
164                    payload_len,
165                    config.limits.max_read_bytes,
166                ),
167            )?;
168            read_root_from_len_prefixed_payload::<E, _>(reader, payload_len, config)
169        }
170    }
171}
172
173pub fn write_with_header_mode<E: Encoding, W: Write>(
174    writer: &mut W,
175    root: &RootTag,
176    mode: HeaderWriteMode,
177) -> Result<()> {
178    match mode {
179        HeaderWriteMode::NoHeader => write_tag::<E, _>(writer, root),
180        HeaderWriteMode::BedrockFileHeader => {
181            let payload = encode_root_payload::<E>(root)?;
182            write_u32_le(writer, BEDROCK_FILE_HEADER_MAGIC)?;
183            write_u32_le(writer, payload_len_u32(payload.len())?)?;
184            writer.write_all(&payload)?;
185            Ok(())
186        }
187        HeaderWriteMode::LevelDatHeader { storage_version } => {
188            let payload = encode_root_payload::<E>(root)?;
189            write_u32_le(writer, storage_version)?;
190            write_u32_le(writer, payload_len_u32(payload.len())?)?;
191            writer.write_all(&payload)?;
192            Ok(())
193        }
194    }
195}
196
197fn read_root_from_len_prefixed_payload<E: Encoding, R: Read>(
198    reader: &mut R,
199    payload_len: usize,
200    config: &NbtReadConfig,
201) -> Result<RootTag> {
202    let mut payload = vec![0u8; payload_len];
203    attach_context(
204        "read_exact",
205        8,
206        Some("header_payload"),
207        reader.read_exact(&mut payload).map_err(Error::from),
208    )?;
209
210    let mut cursor = Cursor::new(payload.as_slice());
211    let root = read_tag_with_config::<E, _>(&mut cursor, config)
212        .map_err(|error| error.with_context("read_header_payload_root", 8, Some("root_payload")))?;
213    let consumed = cursor.position() as usize;
214    let unread = payload_len.saturating_sub(consumed);
215    if unread != 0 {
216        return Err(Error::TrailingPayloadBytes { unread }.with_context(
217            "validate_payload_consumed",
218            8 + consumed,
219            Some("header_payload"),
220        ));
221    }
222    Ok(root)
223}
224
225fn encode_root_payload<E: Encoding>(root: &RootTag) -> Result<Vec<u8>> {
226    let mut payload = Vec::new();
227    write_tag::<E, _>(&mut payload, root)?;
228    Ok(payload)
229}
230
231fn payload_len_u32(payload_len: usize) -> Result<u32> {
232    u32::try_from(payload_len).map_err(|_| Error::LengthOverflow {
233        field: "header_payload_length",
234        max: u32::MAX as usize,
235        actual: payload_len,
236    })
237}
238
239fn read_string<E: Encoding, R: Read>(
240    reader: &mut R,
241    field: &'static str,
242    limits: &NbtLimits,
243    offset: usize,
244) -> Result<String> {
245    let len = attach_context(
246        "read_string_len",
247        offset,
248        Some(field),
249        E::read_string_len(reader),
250    )?;
251    attach_context(
252        "validate_size",
253        offset,
254        Some("root_name_length"),
255        ensure_within_limit("root_name_length", len, limits.max_string_len),
256    )?;
257    let mut bytes = vec![0u8; len];
258    attach_context(
259        "read_exact",
260        offset,
261        Some(field),
262        reader.read_exact(&mut bytes).map_err(Error::from),
263    )?;
264    let decode_res = String::from_utf8(bytes).map_err(|_| Error::InvalidUtf8 { field });
265    attach_context("decode_utf8", offset, Some(field), decode_res)
266}
267
268fn write_string<E: Encoding, W: Write>(writer: &mut W, value: &str) -> Result<()> {
269    E::write_string_len(writer, value.len())?;
270    writer.write_all(value.as_bytes())?;
271    Ok(())
272}
273
274fn read_u32_le<R: Read>(reader: &mut R, offset: usize, field: &'static str) -> Result<u32> {
275    let mut bytes = [0u8; 4];
276    attach_context(
277        "read_exact",
278        offset,
279        Some(field),
280        reader.read_exact(&mut bytes).map_err(Error::from),
281    )?;
282    Ok(u32::from_le_bytes(bytes))
283}
284
285fn write_u32_le<W: Write>(writer: &mut W, value: u32) -> Result<()> {
286    writer.write_all(&value.to_le_bytes())?;
287    Ok(())
288}
289
290fn ensure_within_limit(field: &'static str, actual: usize, max: usize) -> Result<()> {
291    if actual > max {
292        return Err(Error::SizeExceeded { field, max, actual });
293    }
294    Ok(())
295}
296
297fn is_valid_root_tag_type(tag_type: TagType, parse_mode: ParseMode) -> bool {
298    match parse_mode {
299        ParseMode::Strict => matches!(tag_type, TagType::Compound | TagType::List),
300        ParseMode::Compatible => tag_type != TagType::End,
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use std::io::Cursor;
307    use std::io::ErrorKind;
308
309    use indexmap::IndexMap;
310
311    use crate::config::NbtReadConfig;
312    use crate::encoding::{BigEndian, LittleEndian, NetworkLittleEndian};
313    use crate::limits::NbtLimits;
314    use crate::tag::ListTag;
315
316    use super::*;
317
318    fn sample_root(name: &str) -> RootTag {
319        let mut map = IndexMap::new();
320        map.insert("health".to_string(), Tag::Int(20));
321        map.insert("name".to_string(), Tag::String("Steve".to_string()));
322        RootTag::new(name, Tag::Compound(map))
323    }
324
325    #[test]
326    fn root_roundtrip_preserves_name_be() {
327        let root = sample_root("PlayerData");
328        let mut out = Vec::new();
329        write_tag::<BigEndian, _>(&mut out, &root).unwrap();
330
331        let mut input = Cursor::new(out);
332        let decoded = read_tag::<BigEndian, _>(&mut input).unwrap();
333        assert_eq!(decoded, root);
334    }
335
336    #[test]
337    fn root_roundtrip_empty_name_nle() {
338        let root = sample_root("");
339        let mut out = Vec::new();
340        write_tag::<NetworkLittleEndian, _>(&mut out, &root).unwrap();
341
342        let mut input = Cursor::new(out);
343        let decoded = read_tag::<NetworkLittleEndian, _>(&mut input).unwrap();
344        assert_eq!(decoded.name, "");
345        assert_eq!(decoded.payload, root.payload);
346    }
347
348    #[test]
349    fn root_rejects_tag_end_id() {
350        let mut input = Cursor::new(vec![TagType::End.id(), 0x00, 0x00]);
351        let err = read_tag::<LittleEndian, _>(&mut input);
352        let err = err.unwrap_err();
353        assert!(matches!(err.innermost(), Error::InvalidRoot { id: 0 }));
354    }
355
356    #[test]
357    fn root_rejects_primitive_tag_id() {
358        let mut input = Cursor::new(vec![TagType::Int.id(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A]);
359        let err = read_tag::<BigEndian, _>(&mut input).unwrap_err();
360        assert!(matches!(
361            err.innermost(),
362            Error::InvalidRoot { id } if *id == TagType::Int.id()
363        ));
364        assert!(err.has_context("validate_root_tag_type", Some("root_tag_type")));
365    }
366
367    #[test]
368    fn compatible_mode_accepts_primitive_root_tag_id() {
369        let mut input = Cursor::new(vec![TagType::Int.id(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A]);
370        let config = NbtReadConfig::compatible(NbtLimits::default());
371        let decoded = read_tag_with_config::<BigEndian, _>(&mut input, &config).unwrap();
372        assert_eq!(decoded.name, "");
373        assert_eq!(decoded.payload, Tag::Int(42));
374    }
375
376    #[test]
377    fn write_tag_rejects_primitive_root_payload() {
378        let root = RootTag::new("bad", Tag::Int(7));
379        let mut out = Vec::new();
380        let err = write_tag::<BigEndian, _>(&mut out, &root).unwrap_err();
381        assert!(matches!(
382            err.innermost(),
383            Error::InvalidRoot { id } if *id == TagType::Int.id()
384        ));
385    }
386
387    #[test]
388    fn root_list_roundtrip_is_allowed() {
389        let list = ListTag::new(TagType::Int, vec![Tag::Int(1), Tag::Int(2)]).unwrap();
390        let root = RootTag::new("list_root", Tag::List(list));
391        let mut out = Vec::new();
392        write_tag::<BigEndian, _>(&mut out, &root).unwrap();
393
394        let mut input = Cursor::new(out);
395        let decoded = read_tag::<BigEndian, _>(&mut input).unwrap();
396        assert_eq!(decoded, root);
397    }
398
399    #[test]
400    fn no_header_read_stops_at_root_end_and_leaves_trailing_bytes() {
401        let bytes = vec![
402            TagType::Compound.id(),
403            0x00,
404            0x00, // root name len = 0
405            0x00, // empty compound end
406            0xAB, // trailing byte
407        ];
408
409        let mut input = Cursor::new(bytes);
410        let root = read_tag::<BigEndian, _>(&mut input).unwrap();
411        assert!(matches!(root.payload, Tag::Compound(_)));
412        assert_eq!(input.position(), 4);
413    }
414
415    #[test]
416    fn no_header_mode_rejects_bedrock_header_stream_in_strict_mode() {
417        let root = sample_root("BedrockRoot");
418        let mut out = Vec::new();
419        write_with_header_mode::<LittleEndian, _>(
420            &mut out,
421            &root,
422            HeaderWriteMode::BedrockFileHeader,
423        )
424        .unwrap();
425
426        let mut input = Cursor::new(out);
427        let err = read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::NoHeader)
428            .unwrap_err();
429        assert!(matches!(
430            err.innermost(),
431            Error::InvalidRoot { id } if *id == TagType::String.id()
432        ));
433    }
434
435    #[test]
436    fn no_header_mode_rejects_leveldat_header_stream_in_strict_mode() {
437        let root = sample_root("LevelDatRoot");
438        let mut out = Vec::new();
439        write_with_header_mode::<LittleEndian, _>(
440            &mut out,
441            &root,
442            HeaderWriteMode::LevelDatHeader {
443                storage_version: 11,
444            },
445        )
446        .unwrap();
447
448        let mut input = Cursor::new(out);
449        let err = read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::NoHeader)
450            .unwrap_err();
451        assert!(matches!(
452            err.innermost(),
453            Error::InvalidRoot { id } if *id == TagType::IntArray.id()
454        ));
455    }
456
457    #[test]
458    fn bedrock_header_roundtrip() {
459        let root = sample_root("BedrockRoot");
460        let mut out = Vec::new();
461        write_with_header_mode::<LittleEndian, _>(
462            &mut out,
463            &root,
464            HeaderWriteMode::BedrockFileHeader,
465        )
466        .unwrap();
467
468        let mut input = Cursor::new(out);
469        let decoded =
470            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader)
471                .unwrap();
472        assert_eq!(decoded, root);
473    }
474
475    #[test]
476    fn bedrock_header_rejects_invalid_magic() {
477        let root = sample_root("BedrockRoot");
478        let mut payload = Vec::new();
479        write_tag::<LittleEndian, _>(&mut payload, &root).unwrap();
480
481        let mut bytes = Vec::new();
482        bytes.extend_from_slice(&7u32.to_le_bytes());
483        bytes.extend_from_slice(&(payload.len() as u32).to_le_bytes());
484        bytes.extend_from_slice(&payload);
485
486        let mut input = Cursor::new(bytes);
487        let err =
488            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader);
489        let err = err.unwrap_err();
490        assert!(matches!(
491            err.innermost(),
492            Error::InvalidHeader {
493                detail: "bedrock_header_magic_mismatch",
494                expected: Some(BEDROCK_FILE_HEADER_MAGIC),
495                actual: Some(7)
496            }
497        ));
498    }
499
500    #[test]
501    fn bedrock_header_rejects_no_header_stream() {
502        let root = sample_root("NoHeaderRoot");
503        let mut out = Vec::new();
504        write_tag::<LittleEndian, _>(&mut out, &root).unwrap();
505
506        let mut input = Cursor::new(out);
507        let err =
508            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader)
509                .unwrap_err();
510        assert!(matches!(
511            err.innermost(),
512            Error::InvalidHeader {
513                detail: "bedrock_header_magic_mismatch",
514                expected: Some(BEDROCK_FILE_HEADER_MAGIC),
515                ..
516            }
517        ));
518    }
519
520    #[test]
521    fn bedrock_header_rejects_leveldat_stream_when_storage_version_is_not_magic() {
522        let root = sample_root("LevelDatRoot");
523        let mut out = Vec::new();
524        write_with_header_mode::<LittleEndian, _>(
525            &mut out,
526            &root,
527            HeaderWriteMode::LevelDatHeader {
528                storage_version: 11,
529            },
530        )
531        .unwrap();
532
533        let mut input = Cursor::new(out);
534        let err =
535            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader)
536                .unwrap_err();
537        assert!(matches!(
538            err.innermost(),
539            Error::InvalidHeader {
540                detail: "bedrock_header_magic_mismatch",
541                expected: Some(BEDROCK_FILE_HEADER_MAGIC),
542                actual: Some(11)
543            }
544        ));
545    }
546
547    #[test]
548    fn leveldat_header_accepts_non_eight_storage_version() {
549        let root = sample_root("LevelDatRoot");
550        let mut out = Vec::new();
551        write_with_header_mode::<LittleEndian, _>(
552            &mut out,
553            &root,
554            HeaderWriteMode::LevelDatHeader {
555                storage_version: 11,
556            },
557        )
558        .unwrap();
559
560        let mut input = Cursor::new(out);
561        let decoded =
562            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::LevelDatHeader)
563                .unwrap();
564        assert_eq!(decoded, root);
565    }
566
567    #[test]
568    fn leveldat_header_can_read_bedrock_stream_due_to_layout_compatibility() {
569        let root = sample_root("BedrockRoot");
570        let mut out = Vec::new();
571        write_with_header_mode::<LittleEndian, _>(
572            &mut out,
573            &root,
574            HeaderWriteMode::BedrockFileHeader,
575        )
576        .unwrap();
577
578        let mut input = Cursor::new(out);
579        let decoded =
580            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::LevelDatHeader)
581                .unwrap();
582        assert_eq!(decoded, root);
583    }
584
585    #[test]
586    fn header_payload_trailing_bytes_are_rejected() {
587        let root = sample_root("BedrockRoot");
588        let mut payload = Vec::new();
589        write_tag::<LittleEndian, _>(&mut payload, &root).unwrap();
590        payload.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
591
592        let mut bytes = Vec::new();
593        bytes.extend_from_slice(&BEDROCK_FILE_HEADER_MAGIC.to_le_bytes());
594        bytes.extend_from_slice(&(payload.len() as u32).to_le_bytes());
595        bytes.extend_from_slice(&payload);
596
597        let mut input = Cursor::new(bytes);
598        let err =
599            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader);
600        let err = err.unwrap_err();
601        assert!(matches!(
602            err.innermost(),
603            Error::TrailingPayloadBytes { unread: 3 }
604        ));
605    }
606
607    #[test]
608    fn bedrock_header_rejects_payload_size_larger_than_stream() {
609        let root = sample_root("BedrockRoot");
610        let mut payload = Vec::new();
611        write_tag::<LittleEndian, _>(&mut payload, &root).unwrap();
612
613        let mut bytes = Vec::new();
614        bytes.extend_from_slice(&BEDROCK_FILE_HEADER_MAGIC.to_le_bytes());
615        bytes.extend_from_slice(&((payload.len() as u32) + 5).to_le_bytes());
616        bytes.extend_from_slice(&payload);
617
618        let mut input = Cursor::new(bytes);
619        let err =
620            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader)
621                .unwrap_err();
622        assert!(matches!(
623            err.innermost(),
624            Error::Io(io_error) if io_error.kind() == ErrorKind::UnexpectedEof
625        ));
626    }
627
628    #[test]
629    fn bedrock_header_payload_length_over_limit_is_rejected_before_allocation() {
630        let mut bytes = Vec::new();
631        bytes.extend_from_slice(&BEDROCK_FILE_HEADER_MAGIC.to_le_bytes());
632        bytes.extend_from_slice(&(1024u32).to_le_bytes());
633
634        let mut input = Cursor::new(bytes);
635        let limits = NbtLimits::default().with_max_read_bytes(64);
636        let err = read_with_header_mode_with_limits::<LittleEndian, _>(
637            &mut input,
638            HeaderReadMode::BedrockFileHeader,
639            &limits,
640        )
641        .unwrap_err();
642        assert!(matches!(
643            err.innermost(),
644            Error::SizeExceeded {
645                field: "header_payload_length",
646                max: 64,
647                actual: 1024
648            }
649        ));
650    }
651
652    #[test]
653    fn bedrock_header_rejects_payload_size_smaller_than_payload() {
654        let root = sample_root("BedrockRoot");
655        let mut payload = Vec::new();
656        write_tag::<LittleEndian, _>(&mut payload, &root).unwrap();
657        assert!(!payload.is_empty());
658
659        let mut bytes = Vec::new();
660        bytes.extend_from_slice(&BEDROCK_FILE_HEADER_MAGIC.to_le_bytes());
661        bytes.extend_from_slice(&((payload.len() as u32) - 1).to_le_bytes());
662        bytes.extend_from_slice(&payload);
663
664        let mut input = Cursor::new(bytes);
665        let err =
666            read_with_header_mode::<LittleEndian, _>(&mut input, HeaderReadMode::BedrockFileHeader)
667                .unwrap_err();
668        assert!(matches!(
669            err.innermost(),
670            Error::Io(io_error) if io_error.kind() == ErrorKind::UnexpectedEof
671        ));
672    }
673
674    #[test]
675    fn leveldat_header_payload_length_over_limit_is_rejected_before_allocation() {
676        let mut bytes = Vec::new();
677        bytes.extend_from_slice(&(11u32).to_le_bytes());
678        bytes.extend_from_slice(&(1024u32).to_le_bytes());
679
680        let mut input = Cursor::new(bytes);
681        let limits = NbtLimits::default().with_max_read_bytes(64);
682        let err = read_with_header_mode_with_limits::<LittleEndian, _>(
683            &mut input,
684            HeaderReadMode::LevelDatHeader,
685            &limits,
686        )
687        .unwrap_err();
688        assert!(matches!(
689            err.innermost(),
690            Error::SizeExceeded {
691                field: "header_payload_length",
692                max: 64,
693                actual: 1024
694            }
695        ));
696    }
697}