azalea_protocol/
lib.rs

1//! A low-level crate to send and receive Minecraft packets.
2//!
3//! You should probably use [`azalea`] or [`azalea_client`] instead, as
4//! `azalea_protocol` delegates much of the work, such as auth, to the user of
5//! the crate.
6//!
7//! [`azalea`]: https://crates.io/crates/azalea
8//! [`azalea_client`]: https://crates.io/crates/azalea-client
9//!
10//! See [`crate::connect::Connection`] for an example.
11
12// this is necessary for thiserror backtraces
13#![feature(error_generic_member_access)]
14
15use std::{
16    fmt::{self, Display},
17    net::SocketAddr,
18    str::FromStr,
19};
20
21pub mod common;
22#[cfg(feature = "connecting")]
23pub mod connect;
24#[cfg(feature = "packets")]
25pub mod packets;
26pub mod read;
27pub mod resolver;
28pub mod write;
29
30/// A host and port. It's possible that the port doesn't resolve to anything.
31///
32/// # Examples
33///
34/// `ServerAddress` implements TryFrom<&str>, so you can use it like this:
35/// ```
36/// use azalea_protocol::ServerAddress;
37///
38/// let addr = ServerAddress::try_from("localhost:25565").unwrap();
39/// assert_eq!(addr.host, "localhost");
40/// assert_eq!(addr.port, 25565);
41/// ```
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43pub struct ServerAddress {
44    pub host: String,
45    pub port: u16,
46}
47
48impl TryFrom<&str> for ServerAddress {
49    type Error = String;
50
51    /// Convert a Minecraft server address (host:port, the port is optional) to
52    /// a `ServerAddress`
53    fn try_from(string: &str) -> Result<Self, Self::Error> {
54        if string.is_empty() {
55            return Err("Empty string".to_string());
56        }
57        let mut parts = string.split(':');
58        let host = parts.next().ok_or("No host specified")?.to_string();
59        // default the port to 25565
60        let port = parts.next().unwrap_or("25565");
61        let port = u16::from_str(port).map_err(|_| "Invalid port specified")?;
62        Ok(ServerAddress { host, port })
63    }
64}
65impl TryFrom<String> for ServerAddress {
66    type Error = String;
67
68    fn try_from(string: String) -> Result<Self, Self::Error> {
69        ServerAddress::try_from(string.as_str())
70    }
71}
72
73impl From<SocketAddr> for ServerAddress {
74    /// Convert an existing `SocketAddr` into a `ServerAddress`. This just
75    /// converts the ip to a string and passes along the port. The resolver
76    /// will realize it's already an IP address and not do any DNS requests.
77    fn from(addr: SocketAddr) -> Self {
78        ServerAddress {
79            host: addr.ip().to_string(),
80            port: addr.port(),
81        }
82    }
83}
84
85impl Display for ServerAddress {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "{}:{}", self.host, self.port)
88    }
89}
90
91/// Serde deserialization for ServerAddress. This is useful for config file
92/// usage.
93impl<'de> serde::Deserialize<'de> for ServerAddress {
94    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95    where
96        D: serde::Deserializer<'de>,
97    {
98        let string = String::deserialize(deserializer)?;
99        ServerAddress::try_from(string.as_str()).map_err(serde::de::Error::custom)
100    }
101}
102
103/// Serde serialization for ServerAddress. This uses the Display impl, so it
104/// will serialize to a string.
105impl serde::Serialize for ServerAddress {
106    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107    where
108        S: serde::Serializer,
109    {
110        serializer.serialize_str(&self.to_string())
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use std::io::Cursor;
117
118    use uuid::Uuid;
119
120    use crate::{
121        packets::{
122            Packet,
123            game::s_chat::{LastSeenMessagesUpdate, ServerboundChat},
124            login::{ServerboundLoginPacket, s_hello::ServerboundHello},
125        },
126        read::{compression_decoder, read_packet},
127        write::{compression_encoder, serialize_packet, write_packet},
128    };
129
130    #[tokio::test]
131    async fn test_hello_packet() {
132        let packet = ServerboundHello {
133            name: "test".to_string(),
134            profile_id: Uuid::nil(),
135        };
136        let mut stream = Vec::new();
137        write_packet(&packet.into_variant(), &mut stream, None, &mut None)
138            .await
139            .unwrap();
140
141        assert_eq!(
142            stream,
143            [
144                22, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
145            ]
146        );
147
148        let mut stream = Cursor::new(stream);
149
150        let _ = read_packet::<ServerboundLoginPacket, _>(
151            &mut stream,
152            &mut Cursor::new(Vec::new()),
153            None,
154            &mut None,
155        )
156        .await
157        .unwrap();
158    }
159
160    #[tokio::test]
161    async fn test_double_hello_packet() {
162        let packet = ServerboundHello {
163            name: "test".to_string(),
164            profile_id: Uuid::nil(),
165        }
166        .into_variant();
167        let mut stream = Vec::new();
168        write_packet(&packet, &mut stream, None, &mut None)
169            .await
170            .unwrap();
171        write_packet(&packet, &mut stream, None, &mut None)
172            .await
173            .unwrap();
174        let mut stream = Cursor::new(stream);
175
176        let mut buffer = Cursor::new(Vec::new());
177
178        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
179            .await
180            .unwrap();
181        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
182            .await
183            .unwrap();
184    }
185
186    #[tokio::test]
187    async fn test_read_long_compressed_chat() {
188        let compression_threshold = 256;
189
190        let buf = serialize_packet(
191            &ServerboundChat {
192                message: "a".repeat(256),
193                timestamp: 0,
194                salt: 0,
195                signature: None,
196                last_seen_messages: LastSeenMessagesUpdate::default(),
197            }
198            .into_variant(),
199        )
200        .unwrap();
201
202        let buf = compression_encoder(&buf, compression_threshold).unwrap();
203
204        println!("{buf:?}");
205
206        compression_decoder(&mut Cursor::new(&buf), compression_threshold).unwrap();
207    }
208}