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`.
75    ///
76    /// This just converts the IP to a string and passes along the port. The
77    /// resolver will realize it's already an IP address and not do any DNS
78    /// requests.
79    fn from(addr: SocketAddr) -> Self {
80        ServerAddress {
81            host: addr.ip().to_string(),
82            port: addr.port(),
83        }
84    }
85}
86
87impl Display for ServerAddress {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(f, "{}:{}", self.host, self.port)
90    }
91}
92
93/// Serde deserialization for ServerAddress.
94///
95/// This is useful if you're storing the server address in a config file.
96impl<'de> serde::Deserialize<'de> for ServerAddress {
97    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98    where
99        D: serde::Deserializer<'de>,
100    {
101        let string = String::deserialize(deserializer)?;
102        ServerAddress::try_from(string.as_str()).map_err(serde::de::Error::custom)
103    }
104}
105
106/// Serde serialization for ServerAddress.
107///
108/// This uses the Display impl, so it will serialize to a string.
109impl serde::Serialize for ServerAddress {
110    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111    where
112        S: serde::Serializer,
113    {
114        serializer.serialize_str(&self.to_string())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use std::io::Cursor;
121
122    use uuid::Uuid;
123
124    use crate::{
125        packets::{
126            Packet,
127            game::s_chat::{LastSeenMessagesUpdate, ServerboundChat},
128            login::{ServerboundLoginPacket, s_hello::ServerboundHello},
129        },
130        read::{compression_decoder, read_packet},
131        write::{compression_encoder, serialize_packet, write_packet},
132    };
133
134    #[tokio::test]
135    async fn test_hello_packet() {
136        let packet = ServerboundHello {
137            name: "test".to_string(),
138            profile_id: Uuid::nil(),
139        };
140        let mut stream = Vec::new();
141        write_packet(&packet.into_variant(), &mut stream, None, &mut None)
142            .await
143            .unwrap();
144
145        assert_eq!(
146            stream,
147            [
148                22, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
149            ]
150        );
151
152        let mut stream = Cursor::new(stream);
153
154        let _ = read_packet::<ServerboundLoginPacket, _>(
155            &mut stream,
156            &mut Cursor::new(Vec::new()),
157            None,
158            &mut None,
159        )
160        .await
161        .unwrap();
162    }
163
164    #[tokio::test]
165    async fn test_double_hello_packet() {
166        let packet = ServerboundHello {
167            name: "test".to_string(),
168            profile_id: Uuid::nil(),
169        }
170        .into_variant();
171        let mut stream = Vec::new();
172        write_packet(&packet, &mut stream, None, &mut None)
173            .await
174            .unwrap();
175        write_packet(&packet, &mut stream, None, &mut None)
176            .await
177            .unwrap();
178        let mut stream = Cursor::new(stream);
179
180        let mut buffer = Cursor::new(Vec::new());
181
182        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
183            .await
184            .unwrap();
185        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
186            .await
187            .unwrap();
188    }
189
190    #[tokio::test]
191    async fn test_read_long_compressed_chat() {
192        let compression_threshold = 256;
193
194        let buf = serialize_packet(
195            &ServerboundChat {
196                message: "a".repeat(256),
197                timestamp: 0,
198                salt: 0,
199                signature: None,
200                last_seen_messages: LastSeenMessagesUpdate::default(),
201            }
202            .into_variant(),
203        )
204        .unwrap();
205
206        let buf = compression_encoder(&buf, compression_threshold).unwrap();
207
208        println!("{buf:?}");
209
210        compression_decoder(&mut Cursor::new(&buf), compression_threshold).unwrap();
211    }
212}