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