Main part of the protocol complete

This commit is contained in:
Thinkofdeath 2015-09-10 11:49:41 +01:00
parent 1ed844a699
commit 11c33e2d8d
3 changed files with 534 additions and 48 deletions

View File

@ -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<u8>) -> 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<Self, io::Error>;
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>;
}
impl <T> Serializable for Option<T> where T : Serializable {
fn read_from(buf: &mut io::Read) -> Result<Option<T>, 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<String, io::Error> {
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<Empty, io::Error> {
Result::Ok(Empty)
impl Serializable for format::Component {
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error> {
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<Position, io::Error> {
Result::Ok(Position(try!(buf.read_u64::<BigEndian>())))
}
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
try!(buf.write_u64::<BigEndian>(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<bool, io::Error> {
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<i16, io::Error> {
Result::Ok(try!(buf.read_i16::<BigEndian>()))
}
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
try!(buf.write_i16::<BigEndian>(*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<u8, io::Error> {
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<u16, io::Error> {
Result::Ok(try!(buf.read_u16::<BigEndian>()))
@ -160,10 +268,60 @@ impl Serializable for u16 {
}
}
pub trait Lengthable : Serializable + Into<usize> + From<usize> + Copy + Default {}
pub struct LenPrefixed<L: Lengthable, V> {
len: L,
pub data: Vec<V>
}
impl <L: Lengthable, V: Default> LenPrefixed<L, V> {
fn new(data: Vec<V>) -> LenPrefixed<L, V> {
return LenPrefixed {
len: Default::default(),
data: data,
}
}
}
impl <L: Lengthable, V: Serializable> Serializable for LenPrefixed<L, V> {
fn read_from(buf: &mut io::Read) -> Result<LenPrefixed<L, V>, io::Error> {
let len_data : L = try!(Serializable::read_from(buf));
let len : usize = len_data.into();
let mut data : Vec<V> = 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 <L: Lengthable, V: Default> Default for LenPrefixed<L, V> {
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<VarInt, io::Error> {
@ -204,8 +362,21 @@ impl default::Default for VarInt {
fn default() -> VarInt { VarInt(0) }
}
impl convert::Into<usize> for VarInt {
fn into(self) -> usize {
self.0 as usize
}
}
impl convert::From<usize> 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<openssl::EVPCipher>,
compression_threshold: i32,
compression_read: Option<ZlibDecoder<io::Cursor<Vec<u8>>>>, // Pending reset support
compression_write: Option<ZlibEncoder<io::Cursor<Vec<u8>>>>,
}
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<T: PacketType>(&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<packet::Packet, Error> {
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<u8>, 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<usize> {
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<usize> {
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!();
}

View File

@ -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<u8>, public_key: &Vec<u8>) {
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::<Vec<String>>().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<u8>) {
let mut carry = true;
for i in (0 .. data.len()).rev() {
data[i] = !data[i];
if carry {
carry = data[i] == 0xFF;
data[i] += 1;
}
}
}

View File

@ -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<Position> = 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<VarInt, u8> =,
// The verify token from the request encrypted with the
// public key
verify_token: LenPrefixed<VarInt, u8> =,
}
}
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<VarInt, u8> =,
// Token used by the server to verify encryption is working
// correctly
verify_token: LenPrefixed<VarInt, u8> =,
}
// 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,<data>"
// }
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 =,
}
}
}