Skip to main content

basalt_mc_protocol/
registry.rs

1use crate::error::Result;
2use crate::packets::configuration::{
3    ClientboundConfigurationPacket, ServerboundConfigurationPacket,
4};
5use crate::packets::handshake::ServerboundHandshakePacket;
6use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
7use crate::packets::play::{ClientboundPlayPacket, ServerboundPlayPacket};
8use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
9use crate::version::ProtocolVersion;
10
11/// Version-aware packet registry for decoding Minecraft protocol packets.
12///
13/// The registry dispatches raw packet bytes to the correct decoder based on
14/// the packet ID, connection state, and direction (serverbound/clientbound).
15/// It is constructed for a specific protocol version via `PacketRegistry::for_version`.
16///
17/// Currently supports Handshake, Status, and Login states. Configuration and
18/// Play will be added as their packets are generated.
19#[derive(Debug)]
20pub struct PacketRegistry {
21    /// The protocol version this registry was built for.
22    version: ProtocolVersion,
23}
24
25impl PacketRegistry {
26    /// Creates a packet registry for the given protocol version.
27    pub fn for_version(version: ProtocolVersion) -> Self {
28        Self { version }
29    }
30
31    /// Returns the protocol version this registry was built for.
32    pub fn version(&self) -> ProtocolVersion {
33        self.version
34    }
35
36    /// Decodes a serverbound Handshake packet from its ID and payload.
37    pub fn decode_serverbound_handshake(
38        &self,
39        id: i32,
40        buf: &mut &[u8],
41    ) -> Result<ServerboundHandshakePacket> {
42        ServerboundHandshakePacket::decode_by_id(id, buf)
43    }
44
45    /// Decodes a serverbound Status packet from its ID and payload.
46    pub fn decode_serverbound_status(
47        &self,
48        id: i32,
49        buf: &mut &[u8],
50    ) -> Result<ServerboundStatusPacket> {
51        ServerboundStatusPacket::decode_by_id(id, buf)
52    }
53
54    /// Decodes a clientbound Status packet from its ID and payload.
55    pub fn decode_clientbound_status(
56        &self,
57        id: i32,
58        buf: &mut &[u8],
59    ) -> Result<ClientboundStatusPacket> {
60        ClientboundStatusPacket::decode_by_id(id, buf)
61    }
62
63    /// Decodes a serverbound Login packet from its ID and payload.
64    pub fn decode_serverbound_login(
65        &self,
66        id: i32,
67        buf: &mut &[u8],
68    ) -> Result<ServerboundLoginPacket> {
69        ServerboundLoginPacket::decode_by_id(id, buf)
70    }
71
72    /// Decodes a clientbound Login packet from its ID and payload.
73    pub fn decode_clientbound_login(
74        &self,
75        id: i32,
76        buf: &mut &[u8],
77    ) -> Result<ClientboundLoginPacket> {
78        ClientboundLoginPacket::decode_by_id(id, buf)
79    }
80
81    /// Decodes a serverbound Configuration packet from its ID and payload.
82    pub fn decode_serverbound_configuration(
83        &self,
84        id: i32,
85        buf: &mut &[u8],
86    ) -> Result<ServerboundConfigurationPacket> {
87        ServerboundConfigurationPacket::decode_by_id(id, buf)
88    }
89
90    /// Decodes a clientbound Configuration packet from its ID and payload.
91    pub fn decode_clientbound_configuration(
92        &self,
93        id: i32,
94        buf: &mut &[u8],
95    ) -> Result<ClientboundConfigurationPacket> {
96        ClientboundConfigurationPacket::decode_by_id(id, buf)
97    }
98
99    /// Decodes a serverbound Play packet from its ID and payload.
100    pub fn decode_serverbound_play(
101        &self,
102        id: i32,
103        buf: &mut &[u8],
104    ) -> Result<ServerboundPlayPacket> {
105        ServerboundPlayPacket::decode_by_id(id, buf)
106    }
107
108    /// Decodes a clientbound Play packet from its ID and payload.
109    pub fn decode_clientbound_play(
110        &self,
111        id: i32,
112        buf: &mut &[u8],
113    ) -> Result<ClientboundPlayPacket> {
114        ClientboundPlayPacket::decode_by_id(id, buf)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::packets::handshake::ServerboundHandshakeSetProtocol;
122    use crate::packets::status::{
123        ClientboundStatusPing, ClientboundStatusServerInfo, ServerboundStatusPing,
124        ServerboundStatusPingStart,
125    };
126    use basalt_types::{Encode, EncodedSize};
127
128    fn registry() -> PacketRegistry {
129        PacketRegistry::for_version(ProtocolVersion::V1_21)
130    }
131
132    #[test]
133    fn version() {
134        assert_eq!(registry().version(), ProtocolVersion::V1_21);
135    }
136
137    #[test]
138    fn decode_handshake() {
139        let packet = ServerboundHandshakeSetProtocol {
140            protocol_version: 767,
141            server_host: "localhost".into(),
142            server_port: 25565,
143            next_state: 1,
144        };
145        let mut buf = Vec::with_capacity(packet.encoded_size());
146        packet.encode(&mut buf).unwrap();
147
148        let mut cursor = buf.as_slice();
149        let result = registry()
150            .decode_serverbound_handshake(ServerboundHandshakeSetProtocol::PACKET_ID, &mut cursor)
151            .unwrap();
152        assert!(cursor.is_empty());
153        assert_eq!(result, ServerboundHandshakePacket::SetProtocol(packet));
154    }
155
156    #[test]
157    fn decode_status_request() {
158        let mut buf = Vec::new();
159        ServerboundStatusPingStart.encode(&mut buf).unwrap();
160
161        let mut cursor = buf.as_slice();
162        let result = registry()
163            .decode_serverbound_status(ServerboundStatusPingStart::PACKET_ID, &mut cursor)
164            .unwrap();
165        assert!(matches!(result, ServerboundStatusPacket::PingStart(_)));
166    }
167
168    #[test]
169    fn decode_ping_request() {
170        let packet = ServerboundStatusPing { time: 12345 };
171        let mut buf = Vec::new();
172        packet.encode(&mut buf).unwrap();
173
174        let mut cursor = buf.as_slice();
175        let result = registry()
176            .decode_serverbound_status(ServerboundStatusPing::PACKET_ID, &mut cursor)
177            .unwrap();
178        assert_eq!(
179            result,
180            ServerboundStatusPacket::Ping(ServerboundStatusPing { time: 12345 })
181        );
182    }
183
184    #[test]
185    fn decode_status_response() {
186        let packet = ClientboundStatusServerInfo {
187            response: "{}".into(),
188        };
189        let mut buf = Vec::new();
190        packet.encode(&mut buf).unwrap();
191
192        let mut cursor = buf.as_slice();
193        let result = registry()
194            .decode_clientbound_status(ClientboundStatusServerInfo::PACKET_ID, &mut cursor)
195            .unwrap();
196        assert_eq!(
197            result,
198            ClientboundStatusPacket::ServerInfo(ClientboundStatusServerInfo {
199                response: "{}".into()
200            })
201        );
202    }
203
204    #[test]
205    fn decode_ping_response() {
206        let packet = ClientboundStatusPing { time: 67890 };
207        let mut buf = Vec::new();
208        packet.encode(&mut buf).unwrap();
209
210        let mut cursor = buf.as_slice();
211        let result = registry()
212            .decode_clientbound_status(ClientboundStatusPing::PACKET_ID, &mut cursor)
213            .unwrap();
214        assert_eq!(
215            result,
216            ClientboundStatusPacket::Ping(ClientboundStatusPing { time: 67890 })
217        );
218    }
219
220    #[test]
221    fn unknown_handshake_packet() {
222        let mut cursor: &[u8] = &[];
223        assert!(
224            registry()
225                .decode_serverbound_handshake(0xFF, &mut cursor)
226                .is_err()
227        );
228    }
229
230    #[test]
231    fn unknown_status_serverbound() {
232        let mut cursor: &[u8] = &[];
233        assert!(
234            registry()
235                .decode_serverbound_status(0xFF, &mut cursor)
236                .is_err()
237        );
238    }
239
240    #[test]
241    fn unknown_status_clientbound() {
242        let mut cursor: &[u8] = &[];
243        assert!(
244            registry()
245                .decode_clientbound_status(0xFF, &mut cursor)
246                .is_err()
247        );
248    }
249}