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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
use clientbound::ClientboundPacket;
use connection::Connection;
use errors::Result;
use json::AuthenticationResponse;
use serverbound::ServerboundPacket;
use {ClientState, PROTOCOL_VERSION, mojang, serverbound, utils};
use std::{thread, time};
/// Represents a single client connection to a Server.
pub struct Client {
conn: Connection<ClientboundPacket, ServerboundPacket>,
auto_handle: bool,
hide_handled: bool,
}
impl Client {
/// Attempt open the tcp connection to the given host and port, and
/// nothing more. If you use this you must then send all subsequent
/// packets manually to authenticate and so on.
pub fn connect_tcp(host: &str, port: u16) -> Result<Self> {
Ok(Client {
conn: Connection::connect_tcp(host, port)?,
auto_handle: false,
hide_handled: false,
})
}
/// Attempt to connect to the server at the given host and port,
/// completing the usual steps for an unauthenticated login.
///
/// In most cases this is what you will want for offline/local servers
/// and singleplayer. This will establish an unencrypted connection
/// to the server.
///
/// The username is the Minecraft username, and not the Mojang username,
/// (i.e. NOT the email address for migrated accounts.)
///
/// This will set auto_handle and hide_handled to true.
///
/// This will return Ok(Client) on receival of a PlayerAbilities packet,
/// but note that the PlayerAbilities packet and all packets received before
/// it will not be available for consumers. If you need any of those
/// packets, you will want to do the authentication manually.
///
/// This function will time out after 30 seconds (theoretical absolute worst
/// case 60 seconds.)
pub fn connect_unauthenticated(host: &str,
port: u16,
username: &str)
-> Result<Self> {
let timeout = time::Instant::now();
let mut client = Client::connect_tcp(host, port)?;
client.set_auto_handle(true);
client.set_hide_handled(true);
let handshake = serverbound::Handshake::new(PROTOCOL_VERSION,
host.to_string(),
port,
2);
let loginstart = serverbound::LoginStart::new(username.to_string());
client.send(handshake)?;
client.set_clientstate(ClientState::Login);
client.send(loginstart)?;
/* Now we wait for the PlayerAbilities packet from the server */
'wait: loop {
if timeout.elapsed() > time::Duration::new(30, 0) {
bail!("Timed out waiting for LoginSuccess");
}
client.update_inbuf()?;
match client.read_packet()? {
Some(ClientboundPacket::LoginDisconnect(ref p)) => {
bail!("Got LoginDisconnect, reason: {}", p.get_raw_chat());
},
Some(ClientboundPacket::PlayerAbilities(..)) => break 'wait,
Some(ClientboundPacket::EncryptionRequest(..)) => {
bail!("connect_unauthenticated got EncryptionRequest");
},
Some(_) => (),
None => thread::sleep(time::Duration::from_millis(10)),
}
}
Ok(client)
}
/// Authenticate with Mojang, and then connect to the server at the
/// given host and port. In most cases this is what you will want to use
/// to connect to online servers.
///
/// This will establish an encrypted connection to the server.
///
/// Requires that you've already authenticated with Mojang, e.g. by calling
/// yggdrasil::authenticate
///
/// This will set auto_handle and hide_handled to true.
///
/// This will return Ok(Client) on receival of a PlayerAbilities packet,
/// but note that the PlayerAbilities packet and all packets received before
/// it will not be available for consumers. If you need any of those
/// packets, you will want to do the authentication manually.
///
/// This function will time out after 30 seconds (theoretical absolute worst
/// case 60 seconds.)
///
/// # Examples
///
/// ```rust,no_run
/// use ozelot::{mojang, Client};
/// let auth = mojang::Authenticate::new("my_email@example.com".to_string(),
/// "my_password".to_string())
/// .perform().unwrap();
/// let mut client = Client::connect_authenticated("minecraft.example.com",
/// 25565,
/// &auth).unwrap();
/// ```
pub fn connect_authenticated(host: &str,
port: u16,
auth: &AuthenticationResponse)
-> Result<Self> {
let timeout = time::Instant::now();
let mut client = Client::connect_tcp(host, port)?;
client.set_auto_handle(true);
client.set_hide_handled(true);
let handshake = serverbound::Handshake::new(PROTOCOL_VERSION,
host.to_string(),
port,
2);
let loginstart =
serverbound::LoginStart::new(auth.selectedProfile.name.clone());
client.send(handshake)?;
client.set_clientstate(ClientState::Login);
client.send(loginstart)?;
/* Here we wait for a LoginSuccess/EncryptionRequest packet */
'wait: loop {
if timeout.elapsed() > time::Duration::new(30, 0) {
bail!("Timed out waiting for LoginSuccess/EncryptionRequest");
}
client.update_inbuf()?;
match client.read_packet()? {
Some(ClientboundPacket::LoginDisconnect(ref p)) => {
bail!("Got LoginDisconnect, reason: {}", p.get_raw_chat());
},
Some(ClientboundPacket::LoginSuccess(..)) => {
bail!("Logged in unauthenticated")
},
Some(ClientboundPacket::EncryptionRequest(ref p)) => {
let shared_secret = utils::create_shared_secret();
mojang::SessionJoin::new(auth.accessToken.clone(),
auth.selectedProfile.id.clone(),
p.get_server_id(),
&shared_secret,
p.get_public_key())
.perform()?;
let encryptionresponse
= serverbound::EncryptionResponse::new_unencrypted(
&p.get_public_key(),
&shared_secret,
&p.get_verify_token())?;
client.send(encryptionresponse)?;
client.enable_encryption(&shared_secret);
break 'wait;
},
Some(_) => (),
None => thread::sleep(time::Duration::from_millis(10)),
}
}
/* Now we wait for the PlayerAbilities packet from the server */
'wait2: loop {
if timeout.elapsed() > time::Duration::new(30, 0) {
bail!("Timed out waiting for PlayerAbilities packet");
}
client.update_inbuf()?;
match client.read_packet()? {
Some(ClientboundPacket::LoginDisconnect(ref p)) => {
bail!("Got LoginDisconnect, reason: {}", p.get_raw_chat());
},
Some(ClientboundPacket::PlayerAbilities(..)) => break 'wait2,
Some(_) => (),
None => thread::sleep(time::Duration::from_millis(10)),
}
}
Ok(client)
}
/// Try to read some packets from the server.
///
/// This function is nonblocking.
pub fn read(&mut self) -> Result<Vec<ClientboundPacket>> {
self.update_inbuf()?;
let mut ret = Vec::new();
loop {
let packet = self.read_packet()?;
/* push = whether to push the packet to ret */
let mut push = match &packet {
&Some(ClientboundPacket::LoginSuccess(_)) => false,
&Some(ClientboundPacket::SetCompression(_)) => false,
&Some(ClientboundPacket::KeepAlive(_)) => false,
&Some(_) => true,
&None => break,
};
if self.hide_handled == false || self.auto_handle == false {
push = true;
}
if push {
ret.push(packet
.expect("unreachable packet = None in client.read()"));
}
}
Ok(ret)
}
/// Send the given packet to the server that we're connected to.
///
/// This function may block.
pub fn send(&mut self, packet: ServerboundPacket) -> Result<()> {
self.conn.send(packet)
}
/// Whether to automatically handle: KeepAlive, LoginSuccess and
/// SetCompression packets. Most clients won't need to manually deal with
/// these.
pub fn set_auto_handle(&mut self, new: bool) {
self.auto_handle = new;
}
/// Whether or not to hide packets that have been handled by ozelot from the
/// consumer of the library.
///
/// E.g. KeepAlives that are automatically handled if auto_handle = true.
pub fn set_hide_handled(&mut self, new: bool) {
self.hide_handled = new;
}
/// Attempt to close this connection, disconnecting from the server.
///
/// All future sends and reads to this connection will fail.
pub fn close(&mut self) -> Result<()> {
self.conn.close()
}
/// Change the client state of this connection
pub fn set_clientstate(&mut self, new_state: ClientState) {
self.conn.set_clientstate(new_state)
}
/// Enable encryption with the given key.
///
/// It is an error to enable encryption if encryption has already been
/// enabled.
pub fn enable_encryption(&mut self, key: &[u8; 16]) {
self.conn.enable_encryption(key)
}
/// Enable compression.
///
/// It is generally an error to enable compression if compression has
/// already been enabled.
pub fn enable_compression(&mut self, threshold: usize) {
self.conn.enable_compression(threshold)
}
/// Read from the TcpStream and update the incoming buffer.
///
/// This is the only way to actually read from the TcpStream. Unless you
/// know for sure you need to call this, then you do not need to call this.
/// I.e. if you're just using client.read(), then you do not need to call
/// this function.
///
/// This function is nonblocking.
pub fn update_inbuf(&mut self) -> Result<()> {
self.conn.update_inbuf()
}
/// Read a single packet from the internal buffer.
///
/// This is only really useful if you want finegrained control over the
/// processing of packets, or if you want to manually authenticate with
/// the server. In most cases, you'll want to just call client.read().
///
/// You MUST be sure that client.update_inbuf() has been called before this,
/// this function will not attempt to read from the TcpStream, only from the
/// internal buffer.
pub fn read_packet(&mut self) -> Result<Option<ClientboundPacket>> {
let packet = self.conn.read_packet()?;
if self.auto_handle {
match &packet {
&Some(ClientboundPacket::LoginSuccess(..)) => {
self.set_clientstate(ClientState::Play);
},
&Some(ClientboundPacket::SetCompression(ref p)) => {
self.enable_compression(*p.get_threshold() as usize);
},
&Some(ClientboundPacket::KeepAlive(ref p)) => {
let keepalive = serverbound::KeepAlive::new(*p.get_id());
self.send(keepalive)?;
},
_ => (),
}
}
Ok(packet)
}
}