Skip to main content

nbt_rust/
headless.rs

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
10/// Reads a headless NBT value payload using an externally known tag type.
11///
12/// This is the canonical low-level API for protocol fields where the type is
13/// provided by surrounding packet metadata.
14pub 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
34/// Writes a headless NBT value payload (no tag id, no name).
35pub fn write_value<E: Encoding, W: Write>(writer: &mut W, value: &Tag) -> Result<()> {
36    write_payload::<E, _>(writer, value)
37}
38
39/// Alias kept for API ergonomics in packet codec code.
40pub 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
60/// Alias kept for API ergonomics in packet codec code.
61pub fn write_headless<E: Encoding, W: Write>(writer: &mut W, value: &Tag) -> Result<()> {
62    write_value::<E, _>(writer, value)
63}
64
65/// Reads a headless value when only the type id byte is available.
66pub 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
92/// Reads a value where a type id byte is prefixed before headless payload.
93pub 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
126/// Writes a value as `type_id + payload` without root-name.
127pub 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
135/// Convenience decode from bytes for headless payload.
136pub 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
141/// Convenience encode to bytes for headless payload.
142pub 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}