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)]
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            game::s_chat::{LastSeenMessagesUpdate, ServerboundChat},
119            login::{s_hello::ServerboundHello, ServerboundLoginPacket},
120            Packet,
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            [22, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
140        );
141
142        let mut stream = Cursor::new(stream);
143
144        let _ = read_packet::<ServerboundLoginPacket, _>(
145            &mut stream,
146            &mut Cursor::new(Vec::new()),
147            None,
148            &mut None,
149        )
150        .await
151        .unwrap();
152    }
153
154    #[tokio::test]
155    async fn test_double_hello_packet() {
156        let packet = ServerboundHello {
157            name: "test".to_string(),
158            profile_id: Uuid::nil(),
159        }
160        .into_variant();
161        let mut stream = Vec::new();
162        write_packet(&packet, &mut stream, None, &mut None)
163            .await
164            .unwrap();
165        write_packet(&packet, &mut stream, None, &mut None)
166            .await
167            .unwrap();
168        let mut stream = Cursor::new(stream);
169
170        let mut buffer = Cursor::new(Vec::new());
171
172        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
173            .await
174            .unwrap();
175        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
176            .await
177            .unwrap();
178    }
179
180    #[tokio::test]
181    async fn test_read_long_compressed_chat() {
182        let compression_threshold = 256;
183
184        let buf = serialize_packet(
185            &ServerboundChat {
186                message: "a".repeat(256),
187                timestamp: 0,
188                salt: 0,
189                signature: None,
190                last_seen_messages: LastSeenMessagesUpdate::default(),
191            }
192            .into_variant(),
193        )
194        .unwrap();
195
196        let buf = compression_encoder(&buf, compression_threshold).unwrap();
197
198        println!("{:?}", buf);
199
200        compression_decoder(&mut Cursor::new(&buf), compression_threshold).unwrap();
201    }
202}