azalea_protocol/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! A low-level crate to send and receive Minecraft packets.
//!
//! You should probably use [`azalea`] or [`azalea_client`] instead, as
//! `azalea_protocol` delegates much of the work, such as auth, to the user of
//! the crate.
//!
//! [`azalea`]: https://crates.io/crates/azalea
//! [`azalea_client`]: https://crates.io/crates/azalea-client
//!
//! See [`crate::connect::Connection`] for an example.

// these two are necessary for thiserror backtraces
#![feature(error_generic_member_access)]

use std::{fmt::Display, net::SocketAddr, str::FromStr};

#[cfg(feature = "connecting")]
pub mod connect;
#[cfg(feature = "packets")]
pub mod packets;
pub mod read;
pub mod resolver;
pub mod write;

/// A host and port. It's possible that the port doesn't resolve to anything.
///
/// # Examples
///
/// `ServerAddress` implements TryFrom<&str>, so you can use it like this:
/// ```
/// use azalea_protocol::ServerAddress;
///
/// let addr = ServerAddress::try_from("localhost:25565").unwrap();
/// assert_eq!(addr.host, "localhost");
/// assert_eq!(addr.port, 25565);
/// ```
#[derive(Debug, Clone)]
pub struct ServerAddress {
    pub host: String,
    pub port: u16,
}

impl<'a> TryFrom<&'a str> for ServerAddress {
    type Error = String;

    /// Convert a Minecraft server address (host:port, the port is optional) to
    /// a `ServerAddress`
    fn try_from(string: &str) -> Result<Self, Self::Error> {
        if string.is_empty() {
            return Err("Empty string".to_string());
        }
        let mut parts = string.split(':');
        let host = parts.next().ok_or("No host specified")?.to_string();
        // default the port to 25565
        let port = parts.next().unwrap_or("25565");
        let port = u16::from_str(port).map_err(|_| "Invalid port specified")?;
        Ok(ServerAddress { host, port })
    }
}

impl From<SocketAddr> for ServerAddress {
    /// Convert an existing `SocketAddr` into a `ServerAddress`. This just
    /// converts the ip to a string and passes along the port. The resolver
    /// will realize it's already an IP address and not do any DNS requests.
    fn from(addr: SocketAddr) -> Self {
        ServerAddress {
            host: addr.ip().to_string(),
            port: addr.port(),
        }
    }
}

impl Display for ServerAddress {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.host, self.port)
    }
}

/// Serde deserialization for ServerAddress. This is useful for config file
/// usage.
impl<'de> serde::Deserialize<'de> for ServerAddress {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let string = String::deserialize(deserializer)?;
        ServerAddress::try_from(string.as_str()).map_err(serde::de::Error::custom)
    }
}

/// Serde serialization for ServerAddress. This uses the Display impl, so it
/// will serialize to a string.
impl serde::Serialize for ServerAddress {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use bytes::BytesMut;
    use uuid::Uuid;

    use crate::{
        packets::{
            game::serverbound_chat_packet::{LastSeenMessagesUpdate, ServerboundChatPacket},
            login::{serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket},
        },
        read::{compression_decoder, read_packet},
        write::{compression_encoder, serialize_packet, write_packet},
    };

    #[tokio::test]
    async fn test_hello_packet() {
        let packet = ServerboundHelloPacket {
            name: "test".to_string(),
            profile_id: Uuid::nil(),
        }
        .get();
        let mut stream = Vec::new();
        write_packet(&packet, &mut stream, None, &mut None)
            .await
            .unwrap();

        let mut stream = Cursor::new(stream);

        let _ = read_packet::<ServerboundLoginPacket, _>(
            &mut stream,
            &mut BytesMut::new(),
            None,
            &mut None,
        )
        .await
        .unwrap();
    }

    #[tokio::test]
    async fn test_double_hello_packet() {
        let packet = ServerboundHelloPacket {
            name: "test".to_string(),
            profile_id: Uuid::nil(),
        }
        .get();
        let mut stream = Vec::new();
        write_packet(&packet, &mut stream, None, &mut None)
            .await
            .unwrap();
        write_packet(&packet, &mut stream, None, &mut None)
            .await
            .unwrap();
        let mut stream = Cursor::new(stream);

        let mut buffer = BytesMut::new();

        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
            .await
            .unwrap();
        let _ = read_packet::<ServerboundLoginPacket, _>(&mut stream, &mut buffer, None, &mut None)
            .await
            .unwrap();
    }

    #[tokio::test]
    async fn test_read_long_compressed_chat() {
        let compression_threshold = 256;

        let buf = serialize_packet(
            &ServerboundChatPacket {
                message: "a".repeat(256),
                timestamp: 0,
                salt: 0,
                signature: None,
                last_seen_messages: LastSeenMessagesUpdate::default(),
            }
            .get(),
        )
        .unwrap();

        let buf = compression_encoder(&buf, compression_threshold).unwrap();

        println!("{:?}", buf);

        compression_decoder(&mut Cursor::new(&buf), compression_threshold).unwrap();
    }
}