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, 0x00, 0xAB, ];
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}