Skip to main content

nbt_rust/
codec_bridge.rs

1use serde::de::DeserializeOwned;
2use serde::Serialize;
3
4use crate::config::NbtReadConfig;
5use crate::error::Result;
6use crate::protocol_adapter::{ProtocolNbtAdapter, ProtocolNbtEncoding};
7use crate::tag::TagType;
8
9/// Codec profile used by the protocol bridge facade.
10pub trait NbtCodecProfile {
11    const NBT_ENCODING: ProtocolNbtEncoding;
12
13    fn nbt_read_config() -> NbtReadConfig {
14        NbtReadConfig::default()
15    }
16}
17
18/// High-level facade for protocol packet structs.
19///
20/// This is the macro/facade layer for "annotation-like" usage:
21/// `nbt_profile!(MyPacket, net);` then use `encode_nbt_*`/`decode_nbt_*`.
22pub trait NbtCodecFacade: Serialize + DeserializeOwned + NbtCodecProfile + Sized {
23    fn nbt_adapter() -> ProtocolNbtAdapter {
24        ProtocolNbtAdapter {
25            encoding: Self::NBT_ENCODING,
26            read_config: Self::nbt_read_config(),
27        }
28    }
29
30    fn encode_nbt_root(&self, root_name: impl Into<String>) -> Result<Vec<u8>> {
31        Self::nbt_adapter().encode_root(root_name, self)
32    }
33
34    fn decode_nbt_root(bytes: &[u8]) -> Result<Self> {
35        Self::nbt_adapter().decode_root(bytes)
36    }
37
38    fn decode_nbt_root_named(bytes: &[u8]) -> Result<(String, Self)> {
39        Self::nbt_adapter().decode_root_named(bytes)
40    }
41
42    fn encode_nbt_prefixed(&self) -> Result<Vec<u8>> {
43        Self::nbt_adapter().encode_prefixed(self)
44    }
45
46    fn decode_nbt_prefixed(bytes: &[u8]) -> Result<Self> {
47        Self::nbt_adapter().decode_prefixed(bytes)
48    }
49
50    fn encode_nbt_headless(&self) -> Result<(TagType, Vec<u8>)> {
51        Self::nbt_adapter().encode_headless(self)
52    }
53
54    fn decode_nbt_headless(tag_type: TagType, bytes: &[u8]) -> Result<Self> {
55        Self::nbt_adapter().decode_headless(tag_type, bytes)
56    }
57}
58
59impl<T> NbtCodecFacade for T where T: Serialize + DeserializeOwned + NbtCodecProfile {}
60
61/// Declare a codec profile for a type.
62///
63/// `net` => NetworkLittleEndian, `le` => LittleEndian, `be` => BigEndian.
64#[macro_export]
65macro_rules! nbt_profile {
66    ($ty:ty, net) => {
67        impl $crate::codec_bridge::NbtCodecProfile for $ty {
68            const NBT_ENCODING: $crate::ProtocolNbtEncoding = $crate::ProtocolNbtEncoding::Network;
69        }
70    };
71    ($ty:ty, le) => {
72        impl $crate::codec_bridge::NbtCodecProfile for $ty {
73            const NBT_ENCODING: $crate::ProtocolNbtEncoding =
74                $crate::ProtocolNbtEncoding::LittleEndian;
75        }
76    };
77    ($ty:ty, be) => {
78        impl $crate::codec_bridge::NbtCodecProfile for $ty {
79            const NBT_ENCODING: $crate::ProtocolNbtEncoding =
80                $crate::ProtocolNbtEncoding::BigEndian;
81        }
82    };
83}
84
85/// Declare a codec profile with explicit read config.
86#[macro_export]
87macro_rules! nbt_profile_with_config {
88    ($ty:ty, net, $config:expr) => {
89        impl $crate::codec_bridge::NbtCodecProfile for $ty {
90            const NBT_ENCODING: $crate::ProtocolNbtEncoding = $crate::ProtocolNbtEncoding::Network;
91            fn nbt_read_config() -> $crate::NbtReadConfig {
92                $config
93            }
94        }
95    };
96    ($ty:ty, le, $config:expr) => {
97        impl $crate::codec_bridge::NbtCodecProfile for $ty {
98            const NBT_ENCODING: $crate::ProtocolNbtEncoding =
99                $crate::ProtocolNbtEncoding::LittleEndian;
100            fn nbt_read_config() -> $crate::NbtReadConfig {
101                $config
102            }
103        }
104    };
105    ($ty:ty, be, $config:expr) => {
106        impl $crate::codec_bridge::NbtCodecProfile for $ty {
107            const NBT_ENCODING: $crate::ProtocolNbtEncoding =
108                $crate::ProtocolNbtEncoding::BigEndian;
109            fn nbt_read_config() -> $crate::NbtReadConfig {
110                $config
111            }
112        }
113    };
114}
115
116#[cfg(test)]
117mod tests {
118    use serde::{Deserialize, Serialize};
119
120    use crate::config::NbtReadConfig;
121    use crate::limits::NbtLimits;
122    use crate::{Error, TagType};
123
124    use super::*;
125
126    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127    struct NetPacket {
128        username: String,
129        hp: i32,
130        flags: Vec<u8>,
131    }
132
133    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134    struct LePacket {
135        id: i32,
136        values: Vec<i32>,
137    }
138
139    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140    struct StrictListPacket {
141        entries: Vec<i32>,
142    }
143
144    crate::nbt_profile!(NetPacket, net);
145    crate::nbt_profile!(LePacket, le);
146    crate::nbt_profile_with_config!(
147        StrictListPacket,
148        be,
149        NbtReadConfig::strict(NbtLimits::default())
150    );
151
152    #[test]
153    fn net_profile_root_roundtrip() {
154        let input = NetPacket {
155            username: "Steve".to_string(),
156            hp: 20,
157            flags: vec![1, 0, 1],
158        };
159        let bytes = input.encode_nbt_root("Packet").unwrap();
160        let (root_name, output) = NetPacket::decode_nbt_root_named(&bytes).unwrap();
161        assert_eq!(root_name, "Packet");
162        assert_eq!(output, input);
163    }
164
165    #[test]
166    fn le_profile_prefixed_roundtrip() {
167        let input = LePacket {
168            id: 7,
169            values: vec![2, 4, 6, 8],
170        };
171        let bytes = input.encode_nbt_prefixed().unwrap();
172        let output = LePacket::decode_nbt_prefixed(&bytes).unwrap();
173        assert_eq!(output, input);
174    }
175
176    #[test]
177    fn headless_profile_roundtrip() {
178        let input = LePacket {
179            id: 99,
180            values: vec![1, 3, 5],
181        };
182        let (tag_type, bytes) = input.encode_nbt_headless().unwrap();
183        let output = LePacket::decode_nbt_headless(tag_type, &bytes).unwrap();
184        assert_eq!(output, input);
185    }
186
187    #[test]
188    fn profile_with_config_uses_declared_parse_mode() {
189        // BE list payload: elem_type=TAG_End, len=1 (invalid in strict mode)
190        let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x01];
191        let err = StrictListPacket::decode_nbt_headless(TagType::List, &bytes).unwrap_err();
192        assert!(matches!(err.innermost(), Error::InvalidListHeader { .. }));
193    }
194}