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