1use crate::nbt::tag::{NbtCompound, NbtList, NbtTag, tag_id};
2use crate::{Decode, Error, Result};
3
4fn decode_nbt_string(buf: &mut &[u8]) -> Result<String> {
9 let len = u16::decode(buf)? as usize;
10 if buf.len() < len {
11 return Err(Error::BufferUnderflow {
12 needed: len,
13 available: buf.len(),
14 });
15 }
16 let (bytes, rest) = buf.split_at(len);
17 let value = String::from_utf8(bytes.to_vec())?;
18 *buf = rest;
19 Ok(value)
20}
21
22fn decode_tag_payload(tag_type: u8, buf: &mut &[u8]) -> Result<NbtTag> {
29 match tag_type {
30 tag_id::BYTE => Ok(NbtTag::Byte(i8::decode(buf)?)),
31 tag_id::SHORT => Ok(NbtTag::Short(i16::decode(buf)?)),
32 tag_id::INT => Ok(NbtTag::Int(i32::decode(buf)?)),
33 tag_id::LONG => Ok(NbtTag::Long(i64::decode(buf)?)),
34 tag_id::FLOAT => Ok(NbtTag::Float(f32::decode(buf)?)),
35 tag_id::DOUBLE => Ok(NbtTag::Double(f64::decode(buf)?)),
36 tag_id::BYTE_ARRAY => {
37 let len = i32::decode(buf)?;
38 if len < 0 {
39 return Err(Error::Nbt(format!("negative byte array length: {len}")));
40 }
41 let len = len as usize;
42 if buf.len() < len {
43 return Err(Error::BufferUnderflow {
44 needed: len,
45 available: buf.len(),
46 });
47 }
48 let mut data = Vec::with_capacity(len);
49 for _ in 0..len {
50 data.push(i8::decode(buf)?);
51 }
52 Ok(NbtTag::ByteArray(data))
53 }
54 tag_id::STRING => Ok(NbtTag::String(decode_nbt_string(buf)?)),
55 tag_id::LIST => {
56 let element_type = u8::decode(buf)?;
57 let len = i32::decode(buf)?;
58 if len < 0 {
59 return Err(Error::Nbt(format!("negative list length: {len}")));
60 }
61 let len = len as usize;
62 let mut elements = Vec::with_capacity(len.min(buf.len()));
65 for _ in 0..len {
66 elements.push(decode_tag_payload(element_type, buf)?);
67 }
68 Ok(NbtTag::List(NbtList {
69 element_type,
70 elements,
71 }))
72 }
73 tag_id::COMPOUND => {
74 let compound = decode_compound_payload(buf)?;
75 Ok(NbtTag::Compound(compound))
76 }
77 tag_id::INT_ARRAY => {
78 let len = i32::decode(buf)?;
79 if len < 0 {
80 return Err(Error::Nbt(format!("negative int array length: {len}")));
81 }
82 let len = len as usize;
83 let mut data = Vec::with_capacity(len.min(buf.len() / 4));
85 for _ in 0..len {
86 data.push(i32::decode(buf)?);
87 }
88 Ok(NbtTag::IntArray(data))
89 }
90 tag_id::LONG_ARRAY => {
91 let len = i32::decode(buf)?;
92 if len < 0 {
93 return Err(Error::Nbt(format!("negative long array length: {len}")));
94 }
95 let len = len as usize;
96 let mut data = Vec::with_capacity(len.min(buf.len() / 8));
98 for _ in 0..len {
99 data.push(i64::decode(buf)?);
100 }
101 Ok(NbtTag::LongArray(data))
102 }
103 _ => Err(Error::Nbt(format!("unknown NBT tag type: {tag_type}"))),
104 }
105}
106
107fn decode_compound_payload(buf: &mut &[u8]) -> Result<NbtCompound> {
112 let mut compound = NbtCompound::new();
113 loop {
114 let tag_type = u8::decode(buf)?;
115 if tag_type == tag_id::END {
116 return Ok(compound);
117 }
118 let name = decode_nbt_string(buf)?;
119 let tag = decode_tag_payload(tag_type, buf)?;
120 compound.insert(name, tag);
121 }
122}
123
124impl Decode for NbtCompound {
133 fn decode(buf: &mut &[u8]) -> Result<Self> {
138 let tag_type = u8::decode(buf)?;
139 if tag_type != tag_id::COMPOUND {
140 return Err(Error::Nbt(format!(
141 "expected compound root (type 10), got type {tag_type}"
142 )));
143 }
144 decode_compound_payload(buf)
145 }
146}
147
148impl Decode for NbtTag {
153 fn decode(buf: &mut &[u8]) -> Result<Self> {
155 let compound = NbtCompound::decode(buf)?;
156 Ok(NbtTag::Compound(compound))
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::Encode;
164
165 fn roundtrip_compound(compound: &NbtCompound) {
167 let mut buf = Vec::new();
168 compound.encode(&mut buf).unwrap();
169
170 let mut cursor = buf.as_slice();
171 let decoded = NbtCompound::decode(&mut cursor).unwrap();
172 assert!(cursor.is_empty(), "cursor not fully consumed");
173 assert_eq!(decoded, *compound);
174 }
175
176 #[test]
179 fn empty_compound() {
180 roundtrip_compound(&NbtCompound::new());
181 }
182
183 #[test]
186 fn compound_with_byte() {
187 let mut c = NbtCompound::new();
188 c.insert("value", NbtTag::Byte(42));
189 roundtrip_compound(&c);
190 }
191
192 #[test]
193 fn compound_with_short() {
194 let mut c = NbtCompound::new();
195 c.insert("value", NbtTag::Short(1234));
196 roundtrip_compound(&c);
197 }
198
199 #[test]
200 fn compound_with_int() {
201 let mut c = NbtCompound::new();
202 c.insert("value", NbtTag::Int(100000));
203 roundtrip_compound(&c);
204 }
205
206 #[test]
207 fn compound_with_long() {
208 let mut c = NbtCompound::new();
209 c.insert("value", NbtTag::Long(i64::MAX));
210 roundtrip_compound(&c);
211 }
212
213 #[test]
214 fn compound_with_float() {
215 let mut c = NbtCompound::new();
216 c.insert("value", NbtTag::Float(1.5));
217 roundtrip_compound(&c);
218 }
219
220 #[test]
221 fn compound_with_double() {
222 let mut c = NbtCompound::new();
223 c.insert("value", NbtTag::Double(1.23456789));
224 roundtrip_compound(&c);
225 }
226
227 #[test]
228 fn compound_with_string() {
229 let mut c = NbtCompound::new();
230 c.insert("name", NbtTag::String("hello world".into()));
231 roundtrip_compound(&c);
232 }
233
234 #[test]
237 fn compound_with_byte_array() {
238 let mut c = NbtCompound::new();
239 c.insert("data", NbtTag::ByteArray(vec![1, 2, 3, -1, -128, 127]));
240 roundtrip_compound(&c);
241 }
242
243 #[test]
244 fn compound_with_int_array() {
245 let mut c = NbtCompound::new();
246 c.insert("heights", NbtTag::IntArray(vec![100, 200, -300]));
247 roundtrip_compound(&c);
248 }
249
250 #[test]
251 fn compound_with_long_array() {
252 let mut c = NbtCompound::new();
253 c.insert("states", NbtTag::LongArray(vec![i64::MIN, 0, i64::MAX]));
254 roundtrip_compound(&c);
255 }
256
257 #[test]
260 fn compound_with_empty_list() {
261 let mut c = NbtCompound::new();
262 c.insert("items", NbtTag::List(NbtList::new()));
263 roundtrip_compound(&c);
264 }
265
266 #[test]
267 fn compound_with_int_list() {
268 let mut c = NbtCompound::new();
269 let list =
270 NbtList::from_tags(vec![NbtTag::Int(1), NbtTag::Int(2), NbtTag::Int(3)]).unwrap();
271 c.insert("scores", NbtTag::List(list));
272 roundtrip_compound(&c);
273 }
274
275 #[test]
276 fn compound_with_string_list() {
277 let mut c = NbtCompound::new();
278 let list = NbtList::from_tags(vec![
279 NbtTag::String("alpha".into()),
280 NbtTag::String("beta".into()),
281 ])
282 .unwrap();
283 c.insert("names", NbtTag::List(list));
284 roundtrip_compound(&c);
285 }
286
287 #[test]
290 fn nested_compound() {
291 let mut inner = NbtCompound::new();
292 inner.insert("x", NbtTag::Int(10));
293 inner.insert("y", NbtTag::Int(64));
294 inner.insert("z", NbtTag::Int(-20));
295
296 let mut outer = NbtCompound::new();
297 outer.insert("pos", NbtTag::Compound(inner));
298 outer.insert("name", NbtTag::String("marker".into()));
299 roundtrip_compound(&outer);
300 }
301
302 #[test]
303 fn deeply_nested() {
304 let mut level3 = NbtCompound::new();
305 level3.insert("deep", NbtTag::Byte(1));
306
307 let mut level2 = NbtCompound::new();
308 level2.insert("mid", NbtTag::Compound(level3));
309
310 let mut level1 = NbtCompound::new();
311 level1.insert("top", NbtTag::Compound(level2));
312 roundtrip_compound(&level1);
313 }
314
315 #[test]
318 fn list_of_compounds() {
319 let mut item1 = NbtCompound::new();
320 item1.insert("id", NbtTag::String("minecraft:stone".into()));
321 item1.insert("count", NbtTag::Byte(64));
322
323 let mut item2 = NbtCompound::new();
324 item2.insert("id", NbtTag::String("minecraft:dirt".into()));
325 item2.insert("count", NbtTag::Byte(32));
326
327 let list =
328 NbtList::from_tags(vec![NbtTag::Compound(item1), NbtTag::Compound(item2)]).unwrap();
329
330 let mut c = NbtCompound::new();
331 c.insert("inventory", NbtTag::List(list));
332 roundtrip_compound(&c);
333 }
334
335 #[test]
338 fn compound_with_all_types() {
339 let mut c = NbtCompound::new();
340 c.insert("byte", NbtTag::Byte(i8::MAX));
341 c.insert("short", NbtTag::Short(i16::MIN));
342 c.insert("int", NbtTag::Int(42));
343 c.insert("long", NbtTag::Long(i64::MAX));
344 c.insert("float", NbtTag::Float(1.5));
345 c.insert("double", NbtTag::Double(2.5));
346 c.insert("string", NbtTag::String("test".into()));
347 c.insert("byte_array", NbtTag::ByteArray(vec![1, 2]));
348 c.insert("int_array", NbtTag::IntArray(vec![10, 20]));
349 c.insert("long_array", NbtTag::LongArray(vec![100, 200]));
350 c.insert("list", NbtTag::List(NbtList::new()));
351 c.insert("compound", NbtTag::Compound(NbtCompound::new()));
352 roundtrip_compound(&c);
353 }
354
355 #[test]
358 fn nbt_tag_compound_roundtrip() {
359 let mut c = NbtCompound::new();
360 c.insert("value", NbtTag::Int(42));
361 let tag = NbtTag::Compound(c.clone());
362
363 let mut buf = Vec::new();
364 tag.encode(&mut buf).unwrap();
365
366 let mut cursor = buf.as_slice();
367 let decoded = NbtTag::decode(&mut cursor).unwrap();
368 assert!(cursor.is_empty());
369 assert_eq!(decoded, NbtTag::Compound(c));
370 }
371
372 #[test]
375 fn invalid_root_type() {
376 let buf = [tag_id::BYTE, 42];
378 let mut cursor = buf.as_slice();
379 assert!(matches!(
380 NbtCompound::decode(&mut cursor),
381 Err(Error::Nbt(_))
382 ));
383 }
384
385 #[test]
386 fn empty_buffer() {
387 let mut cursor: &[u8] = &[];
388 assert!(NbtCompound::decode(&mut cursor).is_err());
389 }
390
391 #[test]
392 fn truncated_compound() {
393 let buf = [tag_id::COMPOUND];
395 let mut cursor = buf.as_slice();
396 assert!(NbtCompound::decode(&mut cursor).is_err());
397 }
398
399 #[test]
402 fn nbt_tag_non_compound_wraps_in_compound() {
403 use crate::EncodedSize;
404
405 let tag = NbtTag::Int(42);
407 let mut buf = Vec::new();
408 tag.encode(&mut buf).unwrap();
409
410 let mut cursor = buf.as_slice();
412 let decoded = NbtCompound::decode(&mut cursor).unwrap();
413 assert!(cursor.is_empty());
414 assert_eq!(decoded.get(""), Some(&NbtTag::Int(42)));
415
416 assert_eq!(tag.encoded_size(), buf.len());
418 }
419
420 #[test]
421 fn nbt_tag_string_wraps_in_compound() {
422 use crate::EncodedSize;
423
424 let tag = NbtTag::String("hello".into());
425 let mut buf = Vec::new();
426 tag.encode(&mut buf).unwrap();
427 assert_eq!(tag.encoded_size(), buf.len());
428
429 let mut cursor = buf.as_slice();
430 let decoded = NbtCompound::decode(&mut cursor).unwrap();
431 assert_eq!(decoded.get(""), Some(&NbtTag::String("hello".into())));
432 }
433
434 #[test]
435 fn nbt_tag_list_wraps_in_compound() {
436 use crate::EncodedSize;
437
438 let list = NbtList::from_tags(vec![NbtTag::Int(1), NbtTag::Int(2)]).unwrap();
439 let tag = NbtTag::List(list);
440 let mut buf = Vec::new();
441 tag.encode(&mut buf).unwrap();
442 assert_eq!(tag.encoded_size(), buf.len());
443 }
444
445 #[test]
446 fn nbt_tag_byte_array_wraps_in_compound() {
447 use crate::EncodedSize;
448
449 let tag = NbtTag::ByteArray(vec![1, 2, 3]);
450 let mut buf = Vec::new();
451 tag.encode(&mut buf).unwrap();
452 assert_eq!(tag.encoded_size(), buf.len());
453 }
454
455 #[test]
456 fn nbt_tag_int_array_wraps_in_compound() {
457 use crate::EncodedSize;
458
459 let tag = NbtTag::IntArray(vec![100, 200]);
460 let mut buf = Vec::new();
461 tag.encode(&mut buf).unwrap();
462 assert_eq!(tag.encoded_size(), buf.len());
463 }
464
465 #[test]
466 fn nbt_tag_long_array_wraps_in_compound() {
467 use crate::EncodedSize;
468
469 let tag = NbtTag::LongArray(vec![i64::MIN, i64::MAX]);
470 let mut buf = Vec::new();
471 tag.encode(&mut buf).unwrap();
472 assert_eq!(tag.encoded_size(), buf.len());
473 }
474}