1use std::io::{Cursor, Read, Write};
2
3use crate::config::NbtReadConfig;
4use crate::core::{read_payload, 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
10pub fn read_value<E: Encoding, R: Read>(reader: &mut R, tag_type: TagType) -> Result<Tag> {
15 read_payload::<E, _>(reader, tag_type)
16}
17
18pub fn read_value_with_limits<E: Encoding, R: Read>(
19 reader: &mut R,
20 tag_type: TagType,
21 limits: &NbtLimits,
22) -> Result<Tag> {
23 read_payload_with_config::<E, _>(reader, tag_type, &NbtReadConfig::strict(*limits))
24}
25
26pub fn read_value_with_config<E: Encoding, R: Read>(
27 reader: &mut R,
28 tag_type: TagType,
29 config: &NbtReadConfig,
30) -> Result<Tag> {
31 read_payload_with_config::<E, _>(reader, tag_type, config)
32}
33
34pub fn write_value<E: Encoding, W: Write>(writer: &mut W, value: &Tag) -> Result<()> {
36 write_payload::<E, _>(writer, value)
37}
38
39pub fn read_headless<E: Encoding, R: Read>(reader: &mut R, tag_type: TagType) -> Result<Tag> {
41 read_value::<E, _>(reader, tag_type)
42}
43
44pub fn read_headless_with_limits<E: Encoding, R: Read>(
45 reader: &mut R,
46 tag_type: TagType,
47 limits: &NbtLimits,
48) -> Result<Tag> {
49 read_value_with_limits::<E, _>(reader, tag_type, limits)
50}
51
52pub fn read_headless_with_config<E: Encoding, R: Read>(
53 reader: &mut R,
54 tag_type: TagType,
55 config: &NbtReadConfig,
56) -> Result<Tag> {
57 read_value_with_config::<E, _>(reader, tag_type, config)
58}
59
60pub fn write_headless<E: Encoding, W: Write>(writer: &mut W, value: &Tag) -> Result<()> {
62 write_value::<E, _>(writer, value)
63}
64
65pub fn read_headless_by_id<E: Encoding, R: Read>(reader: &mut R, tag_type_id: u8) -> Result<Tag> {
67 let tag_type = TagType::try_from(tag_type_id)
68 .map_err(|error| error.with_context("decode_tag_type", 0, Some("tag_type_id")))?;
69 read_headless::<E, _>(reader, tag_type)
70}
71
72pub fn read_headless_by_id_with_limits<E: Encoding, R: Read>(
73 reader: &mut R,
74 tag_type_id: u8,
75 limits: &NbtLimits,
76) -> Result<Tag> {
77 let tag_type = TagType::try_from(tag_type_id)
78 .map_err(|error| error.with_context("decode_tag_type", 0, Some("tag_type_id")))?;
79 read_headless_with_limits::<E, _>(reader, tag_type, limits)
80}
81
82pub fn read_headless_by_id_with_config<E: Encoding, R: Read>(
83 reader: &mut R,
84 tag_type_id: u8,
85 config: &NbtReadConfig,
86) -> Result<Tag> {
87 let tag_type = TagType::try_from(tag_type_id)
88 .map_err(|error| error.with_context("decode_tag_type", 0, Some("tag_type_id")))?;
89 read_headless_with_config::<E, _>(reader, tag_type, config)
90}
91
92pub fn read_headless_prefixed<E: Encoding, R: Read>(reader: &mut R) -> Result<Tag> {
94 let mut id = [0u8; 1];
95 reader
96 .read_exact(&mut id)
97 .map_err(Error::from)
98 .map_err(|error| error.with_context("read_exact", 0, Some("tag_type_id")))?;
99 read_headless_by_id::<E, _>(reader, id[0])
100}
101
102pub fn read_headless_prefixed_with_limits<E: Encoding, R: Read>(
103 reader: &mut R,
104 limits: &NbtLimits,
105) -> Result<Tag> {
106 let mut id = [0u8; 1];
107 reader
108 .read_exact(&mut id)
109 .map_err(Error::from)
110 .map_err(|error| error.with_context("read_exact", 0, Some("tag_type_id")))?;
111 read_headless_by_id_with_limits::<E, _>(reader, id[0], limits)
112}
113
114pub fn read_headless_prefixed_with_config<E: Encoding, R: Read>(
115 reader: &mut R,
116 config: &NbtReadConfig,
117) -> Result<Tag> {
118 let mut id = [0u8; 1];
119 reader
120 .read_exact(&mut id)
121 .map_err(Error::from)
122 .map_err(|error| error.with_context("read_exact", 0, Some("tag_type_id")))?;
123 read_headless_by_id_with_config::<E, _>(reader, id[0], config)
124}
125
126pub fn write_headless_prefixed<E: Encoding, W: Write>(writer: &mut W, value: &Tag) -> Result<()> {
128 if matches!(value, Tag::End) {
129 return Err(Error::UnexpectedEndTagPayload);
130 }
131 writer.write_all(&[value.tag_type().id()])?;
132 write_headless::<E, _>(writer, value)
133}
134
135pub fn from_headless_bytes<E: Encoding>(bytes: &[u8], tag_type: TagType) -> Result<Tag> {
137 let mut cursor = Cursor::new(bytes);
138 read_headless::<E, _>(&mut cursor, tag_type)
139}
140
141pub fn to_headless_bytes<E: Encoding>(value: &Tag) -> Result<Vec<u8>> {
143 let mut out = Vec::new();
144 write_headless::<E, _>(&mut out, value)?;
145 Ok(out)
146}
147
148#[cfg(test)]
149mod tests {
150 use std::io::Cursor;
151
152 use indexmap::IndexMap;
153
154 use crate::encoding::{BigEndian, NetworkLittleEndian};
155 use crate::error::Error;
156 use crate::tag::ListTag;
157
158 use super::*;
159
160 #[test]
161 fn value_aliases_roundtrip_be() {
162 let value = Tag::String("hello".to_string());
163 let mut out = Vec::new();
164 write_value::<BigEndian, _>(&mut out, &value).unwrap();
165
166 let mut input = Cursor::new(out);
167 let decoded = read_headless::<BigEndian, _>(&mut input, TagType::String).unwrap();
168 assert_eq!(decoded, value);
169 }
170
171 #[test]
172 fn prefixed_roundtrip_nle_compound() {
173 let mut map = IndexMap::new();
174 map.insert("score".to_string(), Tag::Int(42));
175 map.insert(
176 "flags".to_string(),
177 Tag::List(ListTag::new(TagType::Byte, vec![Tag::Byte(1), Tag::Byte(0)]).unwrap()),
178 );
179 let value = Tag::Compound(map);
180
181 let mut out = Vec::new();
182 write_headless_prefixed::<NetworkLittleEndian, _>(&mut out, &value).unwrap();
183
184 let mut input = Cursor::new(out);
185 let decoded = read_headless_prefixed::<NetworkLittleEndian, _>(&mut input).unwrap();
186 assert_eq!(decoded, value);
187 }
188
189 #[test]
190 fn headless_by_id_rejects_unknown_tag_id() {
191 let mut input = Cursor::new(Vec::<u8>::new());
192 let err = read_headless_by_id::<BigEndian, _>(&mut input, 99);
193 let err = err.unwrap_err();
194 assert!(matches!(err.innermost(), Error::UnknownTag { id: 99 }));
195 }
196
197 #[test]
198 fn headless_prefixed_rejects_tag_end_payload() {
199 let mut input = Cursor::new(vec![TagType::End.id()]);
200 let err = read_headless_prefixed::<BigEndian, _>(&mut input);
201 let err = err.unwrap_err();
202 assert!(matches!(err.innermost(), Error::UnexpectedEndTagPayload));
203 }
204
205 #[test]
206 fn to_from_headless_bytes_helpers_work() {
207 let value = Tag::IntArray(vec![1, 2, 3, 4]);
208 let bytes = to_headless_bytes::<BigEndian>(&value).unwrap();
209 let decoded = from_headless_bytes::<BigEndian>(&bytes, TagType::IntArray).unwrap();
210 assert_eq!(decoded, value);
211 }
212
213 #[test]
214 fn to_from_headless_bytes_preserves_long_array_variant() {
215 let value = Tag::LongArray(vec![-5, 0, 7, i64::MIN, i64::MAX]);
216 let bytes = to_headless_bytes::<BigEndian>(&value).unwrap();
217 let decoded = from_headless_bytes::<BigEndian>(&bytes, TagType::LongArray).unwrap();
218 assert_eq!(decoded, value);
219 }
220
221 #[test]
222 fn write_headless_prefixed_rejects_end_tag_without_writing_bytes() {
223 let mut out = Vec::new();
224 let err = write_headless_prefixed::<BigEndian, _>(&mut out, &Tag::End).unwrap_err();
225 assert!(matches!(err, Error::UnexpectedEndTagPayload));
226 assert!(out.is_empty());
227 }
228}