From 11c33e2d8d5f30642dd370dd54f9a615592df9ff Mon Sep 17 00:00:00 2001 From: Thinkofdeath Date: Thu, 10 Sep 2015 11:49:41 +0100 Subject: [PATCH] Main part of the protocol complete --- protocol/src/protocol/mod.rs | 396 +++++++++++++++++++++++++++++--- protocol/src/protocol/mojang.rs | 65 ++++++ protocol/src/protocol/packet.rs | 121 +++++++++- 3 files changed, 534 insertions(+), 48 deletions(-) create mode 100644 protocol/src/protocol/mojang.rs diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs index 4bba560..ace9268 100644 --- a/protocol/src/protocol/mod.rs +++ b/protocol/src/protocol/mod.rs @@ -1,12 +1,20 @@ #![allow(dead_code)] extern crate byteorder; +extern crate hyper; +extern crate steven_openssl as openssl; +extern crate flate2; +extern crate serde_json; +pub mod mojang; + +use format; use std::default; use std::net::TcpStream; use std::io; use std::io::{Write, Read}; use std::convert; -use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use self::byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use self::flate2::read::{ZlibDecoder, ZlibEncoder}; /// Helper macro for defining packets #[macro_export] @@ -14,13 +22,12 @@ macro_rules! state_packets { ($($state:ident $stateName:ident { $($dir:ident $dirName:ident { $($name:ident => $id:expr { - $($field:ident: $field_type:ident),+ - }),* + $($field:ident: $field_type:ty = $(when ($cond:expr))*, )+ + })* })+ })+) => { use protocol::*; use std::io; - use protocol::{Serializable}; pub enum Packet { $( @@ -36,9 +43,10 @@ macro_rules! state_packets { pub mod $state { $( pub mod $dir { + #![allow(unused_imports)] use protocol::*; use std::io; - use protocol::{Serializable}; + use format; $( #[derive(Default)] @@ -52,7 +60,9 @@ macro_rules! state_packets { fn write(self, buf: &mut Vec) -> Result<(), io::Error> { $( - try!(self.$field.write_to(buf)); + if true $(&& ($cond(&self)))* { + try!(self.$field.write_to(buf)); + } )+ Result::Ok(()) @@ -76,9 +86,12 @@ macro_rules! state_packets { match id { $( $id => { - let mut packet : $state::$dir::$name = $state::$dir::$name::default(); + use self::$state::$dir::$name; + let mut packet : $name = $name::default(); $( - packet.$field = try!($field_type::read_from(&mut buf)); + if true $(&& ($cond(&packet)))* { + packet.$field = try!(Serializable::read_from(&mut buf)); + } )+ Result::Ok(Option::Some(Packet::$name(packet))) }, @@ -97,11 +110,23 @@ macro_rules! state_packets { pub mod packet; -trait Serializable { +pub trait Serializable { fn read_from(buf: &mut io::Read) -> Result; fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>; } +impl Serializable for Option where T : Serializable { + fn read_from(buf: &mut io::Read) -> Result, io::Error> { + Result::Ok(Some(try!(T::read_from(buf)))) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + if self.is_some() { + try!(self.as_ref().unwrap().write_to(buf)); + } + Result::Ok(()) + } +} + impl Serializable for String { fn read_from(buf: &mut io::Read) -> Result { let len = try!(VarInt::read_from(buf)).0; @@ -117,17 +142,90 @@ impl Serializable for String { } } -impl Serializable for Empty { - fn read_from(buf: &mut io::Read) -> Result { - Result::Ok(Empty) +impl Serializable for format::Component { + fn read_from(buf: &mut io::Read) -> Result { + let len = try!(VarInt::read_from(buf)).0; + let mut ret = String::new(); + try!(buf.take(len as u64).read_to_string(&mut ret)); + let val : serde_json::Value = serde_json::from_str(&ret[..]).unwrap(); + Result::Ok(Self::from_value(&val)) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + let val = serde_json::to_string(&self.to_value()).unwrap(); + let bytes = val.as_bytes(); + try!(VarInt(bytes.len() as i32).write_to(buf)); + try!(buf.write_all(bytes)); Result::Ok(()) } } -impl Default for Empty { - fn default() -> Empty { Empty } +pub struct Position(u64); + +impl Position { + fn new(x: i32, y: i32, z: i32) -> Position { + Position( + (((x as u64) & 0x3FFFFFF) << 38) | + (((y as u64) & 0xFFF) << 26) | + ((z as u64) & 0x3FFFFFF) + ) + } + + fn get_x(&self) -> i32 { + ((self.0 as i64) >> 38) as i32 + } + + fn get_y(&self) -> i32 { + (((self.0 as i64) >> 26) & 0xFFF) as i32 + } + + fn get_z(&self) -> i32 { + ((self.0 as i64) << 38 >> 38) as i32 + } +} + +impl Default for Position { + fn default() -> Position { + Position(0) + } +} + +impl Serializable for Position { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(Position(try!(buf.read_u64::()))) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u64::(self.0)); + Result::Ok(()) + } +} + +impl Serializable for () { + fn read_from(_: &mut io::Read) -> Result<(), io::Error> { + Result::Ok(()) + } + fn write_to(&self, _: &mut io::Write) -> Result<(), io::Error> { + Result::Ok(()) + } +} + +impl Serializable for bool { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_u8()) != 0) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u8(if *self { 1 } else { 0 })); + Result::Ok(()) + } +} + +impl Serializable for i16 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_i16::())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_i16::(*self)); + Result::Ok(()) + } } impl Serializable for i32 { @@ -150,6 +248,16 @@ impl Serializable for i64 { } } +impl Serializable for u8 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_u8())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u8(*self)); + Result::Ok(()) + } +} + impl Serializable for u16 { fn read_from(buf: &mut io::Read) -> Result { Result::Ok(try!(buf.read_u16::())) @@ -160,10 +268,60 @@ impl Serializable for u16 { } } +pub trait Lengthable : Serializable + Into + From + Copy + Default {} + +pub struct LenPrefixed { + len: L, + pub data: Vec +} + +impl LenPrefixed { + fn new(data: Vec) -> LenPrefixed { + return LenPrefixed { + len: Default::default(), + data: data, + } + } +} + +impl Serializable for LenPrefixed { + fn read_from(buf: &mut io::Read) -> Result, io::Error> { + let len_data : L = try!(Serializable::read_from(buf)); + let len : usize = len_data.into(); + let mut data : Vec = Vec::with_capacity(len); + for _ in 0 .. len { + data.push(try!(Serializable::read_from(buf))); + } + Result::Ok(LenPrefixed{len: len_data, data: data}) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + let len_data : L = self.data.len().into(); + try!(len_data.write_to(buf)); + let ref data = self.data; + for val in data { + try!(val.write_to(buf)); + } + Result::Ok(()) + } +} + +impl Default for LenPrefixed { + fn default() -> Self { + LenPrefixed { + len: default::Default::default(), + data: default::Default::default() + } + } +} + /// VarInt have a variable size (between 1 and 5 bytes) when encoded based /// on the size of the number +#[derive(Clone, Copy)] pub struct VarInt(i32); +impl Lengthable for VarInt {} + impl Serializable for VarInt { /// Decodes a VarInt from the Reader fn read_from(buf: &mut io::Read) -> Result { @@ -204,8 +362,21 @@ impl default::Default for VarInt { fn default() -> VarInt { VarInt(0) } } +impl convert::Into for VarInt { + fn into(self) -> usize { + self.0 as usize + } +} + +impl convert::From for VarInt { + fn from(u: usize) -> VarInt { + VarInt(u as i32) + } +} + /// Direction is used to define whether packets are going to the /// server or the client. +#[derive(Clone, Copy)] pub enum Direction { Serverbound, Clientbound @@ -252,16 +423,18 @@ impl ::std::fmt::Display for Error { } } - -/// Helper for empty structs -pub struct Empty; - pub struct Conn { stream: TcpStream, host: String, port: u16, direction: Direction, state: State, + + cipher: Option, + + compression_threshold: i32, + compression_read: Option>>>, // Pending reset support + compression_write: Option>>>, } impl Conn { @@ -278,27 +451,58 @@ impl Conn { port: parts[1].parse().unwrap(), direction: Direction::Serverbound, state: State::Handshaking, + cipher: Option::None, + compression_threshold: -1, + compression_read: Option::None, + compression_write: Option::None, }) } - // TODO: compression and encryption - pub fn write_packet(&mut self, packet: T) -> Result<(), Error> { let mut buf = Vec::new(); try!(VarInt(packet.packet_id()).write_to(&mut buf)); try!(packet.write(&mut buf)); - try!(VarInt(buf.len() as i32).write_to(&mut self.stream)); - try!(self.stream.write_all(&buf.into_boxed_slice())); + + let mut extra = if self.compression_threshold >= 0 { 1 } else { 0 }; + if self.compression_threshold >= 0 && buf.len() as i32 > self.compression_threshold { + extra = 0; + let uncompressed_size = buf.len(); + let mut new = Vec::new(); + try!(VarInt(uncompressed_size as i32).write_to(&mut new)); + let mut write = self.compression_write.as_mut().unwrap(); + write.reset(io::Cursor::new(buf)); + try!(write.read_to_end(&mut new)); + buf = new; + } + + try!(VarInt(buf.len() as i32 + extra).write_to(self)); + if self.compression_threshold >= 0 && extra == 1 { + try!(VarInt(0).write_to(self)); + } + try!(self.write_all(&buf.into_boxed_slice())); Result::Ok(()) } pub fn read_packet(&mut self) -> Result { - let len = try!(VarInt::read_from(&mut self.stream)).0 as usize; + let len = try!(VarInt::read_from(self)).0 as usize; let mut ibuf = Vec::with_capacity(len); - try!((&mut self.stream).take(len as u64).read_to_end(&mut ibuf)); + try!(self.take(len as u64).read_to_end(&mut ibuf)); let mut buf = io::Cursor::new(ibuf); + + if self.compression_threshold >= 0 { + let uncompressed_size = try!(VarInt::read_from(&mut buf)).0; + if uncompressed_size != 0 { + let mut new = Vec::with_capacity(uncompressed_size as usize); + { + let mut reader = self.compression_read.as_mut().unwrap(); + reader.reset(buf); + try!(reader.read_to_end(&mut new)); + } + buf = io::Cursor::new(new); + } + } let id = try!(VarInt::read_from(&mut buf)).0; let dir = match self.direction { @@ -317,7 +521,70 @@ impl Conn { } Result::Ok(val) }, - None => Result::Err(Error::Err("missing packet".to_string())) + // FIXME + None => Result::Ok(packet::Packet::StatusRequest(packet::status::serverbound::StatusRequest{empty:()}))//Result::Err(Error::Err("missing packet".to_string())) + } + } + + pub fn enable_encyption(&mut self, key: &Vec, decrypt: bool) { + self.cipher = Option::Some(openssl::EVPCipher::new(key, key, decrypt)); + } + + pub fn set_compresssion(&mut self, threshold: i32, read: bool) { + self.compression_threshold = threshold; + if !read { + self.compression_write = Some(ZlibEncoder::new(io::Cursor::new(Vec::new()), flate2::Compression::Default)); + } else { + self.compression_read = Some(ZlibDecoder::new(io::Cursor::new(Vec::new()))); + } + } +} + +impl Read for Conn { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.cipher.as_mut() { + Option::None => self.stream.read(buf), + Option::Some(cipher) => { + let ret = try!(self.stream.read(buf)); + let data = cipher.decrypt(&buf[..ret]); + for i in 0 .. ret { + buf[i] = data[i]; + } + Ok(ret) + }, + } + } +} + +impl Write for Conn { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.cipher.as_mut() { + Option::None => self.stream.write(buf), + Option::Some(cipher) => { + let data = cipher.encrypt(buf); + try!(self.stream.write_all(&data[..])); + Ok(buf.len()) + }, + } + } + + fn flush(&mut self) -> io::Result<()> { + self.stream.flush() + } +} + +impl Clone for Conn { + fn clone(&self) -> Self { + Conn { + stream: self.stream.try_clone().unwrap(), + host: self.host.clone(), + port: self.port, + direction: self.direction, + state: self.state, + cipher: Option::None, + compression_threshold: self.compression_threshold, + compression_read: Option::None, + compression_write: Option::None, } } } @@ -336,22 +603,77 @@ fn test() { protocol_version: VarInt(69), host: "localhost".to_string(), port: 25565, - next: VarInt(1), + next: VarInt(2), }).unwrap(); - c.state = State::Status; - c.write_packet(packet::status::serverbound::StatusRequest{empty: Empty}).unwrap(); + c.state = State::Login; + c.write_packet(packet::login::serverbound::LoginStart{username: "Think".to_string()}).unwrap(); - match c.read_packet().unwrap() { - packet::Packet::StatusResponse(val) => println!("{}", val.status), + let packet = match c.read_packet().unwrap() { + packet::Packet::EncryptionRequest(val) => val, _ => panic!("Wrong packet"), - } + }; - c.write_packet(packet::status::serverbound::StatusPing{ping: 4433}).unwrap(); + let mut key = openssl::PublicKey::new(&packet.public_key.data); + let shared = openssl::gen_random(16); - match c.read_packet().unwrap() { - packet::Packet::StatusPong(val) => println!("{}", val.ping), - _ => panic!("Wrong packet"), - } + let shared_e = key.encrypt(&shared); + let token_e = key.encrypt(&packet.verify_token.data); - panic!("TODO!"); + let profile = mojang::Profile{ + username: "Think".to_string(), + id: "b1184d43168441cfa2128b9a3df3b6ab".to_string(), + access_token: "".to_string() + }; + + profile.join_server(&packet.server_id, &shared, &packet.public_key.data); + + c.write_packet(packet::login::serverbound::EncryptionResponse{ + shared_secret: LenPrefixed::new(shared_e), + verify_token: LenPrefixed::new(token_e), + }); + + let mut read = c.clone(); + let mut write = c.clone(); + + read.enable_encyption(&shared, true); + write.enable_encyption(&shared, false); + + loop { match read.read_packet().unwrap() { + packet::Packet::LoginDisconnect(val) => { + panic!("Discconect {}", val.reason); + }, + packet::Packet::SetInitialCompression(val) => { + read.set_compresssion(val.threshold.0, true); + write.set_compresssion(val.threshold.0, false); + println!("Compression: {}", val.threshold.0) + }, + packet::Packet::LoginSuccess(val) => { + println!("Login: {} {}", val.username, val.uuid); + read.state = State::Play; + write.state = State::Play; + break; + } + _ => panic!("Unknown packet"), + } } + + let mut first = true; + let mut count = 0; + loop { match read.read_packet().unwrap() { + packet::Packet::ServerMessage(val) => println!("MSG: {}", val.message), + _ => { + if first { + println!("got packet"); + write.write_packet(packet::play::serverbound::ChatMessage{ + message: "Hello world".to_string(), + }); + first = false; + } + count += 1; + if count > 200 { + break; + } + } + } } + + unimplemented!(); } diff --git a/protocol/src/protocol/mojang.rs b/protocol/src/protocol/mojang.rs new file mode 100644 index 0000000..2941ad8 --- /dev/null +++ b/protocol/src/protocol/mojang.rs @@ -0,0 +1,65 @@ +extern crate steven_openssl as openssl; +extern crate serde_json; +extern crate hyper; + +pub struct Profile { + pub username: String, + pub id: String, + pub access_token: String +} + +const JOIN_URL: &'static str = "https://sessionserver.mojang.com/session/minecraft/join"; + +impl Profile { + pub fn join_server(&self, server_id: &String, shared_key: &Vec, public_key: &Vec) { + let mut sha1 = openssl::SHA1::new(); + sha1.update(server_id.as_bytes()); + sha1.update(&shared_key[..]); + sha1.update(&public_key[..]); + let mut hash = sha1.bytes(); + + // Mojang uses a hex method which allows for + // negatives so we have to account for that. + let negative = hash[0] & 0x80 == 0x80; + if negative { + twos_compliment(&mut hash); + } + let hash_str = hash.iter().map(|b| format!("{:02X}", b)).collect::>().connect(""); + let hash_val = hash_str.trim_matches('0'); + let hash_str = if negative { + "-".to_owned() + &hash_val[..] + } else { + hash_val.to_owned() + }; + + let join_msg = serde_json::builder::ObjectBuilder::new() + .insert("accessToken", &self.access_token) + .insert("selectedProfile", &self.id) + .insert("serverId", hash_str) + .unwrap(); + let join = serde_json::to_string(&join_msg).unwrap(); + + let client = hyper::Client::new(); + let res = client.post(JOIN_URL) + .body(&join) + .header(hyper::header::ContentType("application/json".parse().unwrap())) + .send().unwrap(); + + let ret: serde_json::Value = match serde_json::from_reader(res) { + Result::Ok(val) => val, + Result::Err(_) => return, + }; + panic!("{:?}", ret); + } +} + +fn twos_compliment(data: &mut Vec) { + let mut carry = true; + for i in (0 .. data.len()).rev() { + data[i] = !data[i]; + if carry { + carry = data[i] == 0xFF; + data[i] += 1; + } + } +} diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs index 4819c8a..02d9e68 100644 --- a/protocol/src/protocol/packet.rs +++ b/protocol/src/protocol/packet.rs @@ -17,15 +17,15 @@ state_packets!( // than the hostname due to the protocol not providing // any system for custom information to be transfered // by the client to the server until after login. - Handshake => 0 { + Handshake => 0x00 { // The protocol version of the connecting client - protocol_version: VarInt, + protocol_version: VarInt =, // The hostname the client connected to - host: String, + host: String =, // The port the client connected to - port: u16, + port: u16 =, // The next protocol state the client wants - next: VarInt + next: VarInt =, } } clientbound Clientbound { @@ -33,14 +33,113 @@ state_packets!( } play Play { serverbound Serverbound { + // TabComplete is sent by the client when the client presses tab in + // the chat box. + TabComplete => 0x00 { + text: String =, + has_target: bool =, + target: Option = when(|p: &TabComplete| p.has_target == true), + } + // ChatMessage is sent by the client when it sends a chat message or + // executes a command (prefixed by '/'). + ChatMessage => 0x01 { + message: String =, + } + // ClientStatus is sent to update the client's status + ClientStatus => 0x02 { + action_id: VarInt =, + } + // ClientSettings is sent by the client to update its current settings. + ClientSettings => 0x03 { + locale: String =, + view_distance: u8 =, + chat_mode: u8 =, + chat_colors: bool =, + displayed_skin_parts: u8 =, + main_hand: VarInt =, + } + // ConfirmTransactionServerbound is a reply to ConfirmTransaction. + ConfirmTransactionServerbound => 0x04 { + id: u8 =, + action_number: i16 =, + accepted: bool =, + } + // EnchantItem is sent when the client enchants an item. + EnchantItem => 0x05 { + id: u8 =, + enchantment: u8 =, + } + // ClickWindow is sent when the client clicks in a window. + ClickWindow => 0x06 { + id: u8 =, + slot: i16 =, + button: u8 =, + action_number: u16 =, + mode: u8 =, + clicked_item: ()=, // TODO + } } clientbound Clientbound { + ServerMessage => 15 { + message: format::Component =, + position: u8 =, + } } } login Login { serverbound Serverbound { + // LoginStart is sent immeditately after switching into the login + // state. The passed username is used by the server to authenticate + // the player in online mode. + LoginStart => 0 { + username: String =, + } + // EncryptionResponse is sent as a reply to EncryptionRequest. All + // packets following this one must be encrypted with AES/CFB8 + // encryption. + EncryptionResponse => 1 { + // The key for the AES/CFB8 cipher encrypted with the + // public key + shared_secret: LenPrefixed =, + // The verify token from the request encrypted with the + // public key + verify_token: LenPrefixed =, + } } clientbound Clientbound { + // LoginDisconnect is sent by the server if there was any issues + // authenticating the player during login or the general server + // issues (e.g. too many players). + LoginDisconnect => 0 { + reason: format::Component =, + } + // EncryptionRequest is sent by the server if the server is in + // online mode. If it is not sent then its assumed the server is + // in offline mode. + EncryptionRequest => 1 { + // Generally empty, left in from legacy auth + // but is still used by the client if provided + server_id: String =, + // A RSA Public key serialized in x.509 PRIX format + public_key: LenPrefixed =, + // Token used by the server to verify encryption is working + // correctly + verify_token: LenPrefixed =, + } + // LoginSuccess is sent by the server if the player successfully + // authenicates with the session servers (online mode) or straight + // after LoginStart (offline mode). + LoginSuccess => 2 { + // String encoding of a uuid (with hyphens) + uuid: String =, + username: String =, + } + // SetInitialCompression sets the compression threshold during the + // login state. + SetInitialCompression => 3 { + // Threshold where a packet should be sent compressed + threshold: VarInt =, + } } } status Status { @@ -50,14 +149,14 @@ state_packets!( // to signal the server to send a StatusResponse to the // client StatusRequest => 0 { - empty: Empty - }, + empty: () =, + } // StatusPing is sent by the client after recieving a // StatusResponse. The client uses the time from sending // the ping until the time of recieving a pong to measure // the latency between the client and the server. StatusPing => 1 { - ping: i64 + ping: i64 =, } } clientbound Clientbound { @@ -83,13 +182,13 @@ state_packets!( // "favicon": "data:image/png;base64," // } StatusResponse => 0 { - status: String - }, + status: String =, + } // StatusPong is sent as a reply to a StatusPing. // The Time field should be exactly the same as the // one sent by the client. StatusPong => 1 { - ping: i64 + ping: i64 =, } } }