Main part of the protocol complete

This commit is contained in:
Thinkofdeath 2015-09-10 11:49:41 +01:00
parent c015ae9e55
commit 6f6d3c96ca
9 changed files with 1014 additions and 50 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
*.lock
/target
target/

View File

@ -8,7 +8,15 @@ authors = [ "Thinkofdeath <thinkofdeath@spigotmc.org>" ]
rustc-serialize = "0.3"
glfw = "0.1.0"
byteorder = "0.3.13"
hyper = "0.6.13"
serde = "0.6.0"
serde_json = "0.6.0"
flate2 = "0.2.9"
[dependencies.steven_gl]
path = "./gl"
version = "0"
[dependencies.steven_openssl]
path = "./openssl"
version = "0"

7
openssl/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "steven_openssl"
version = "0.0.1"
authors = [ "Thinkofdeath <thinkofdeath@spigotmc.org>" ]
[dependencies]
libc = "*"

209
openssl/src/lib.rs Normal file
View File

@ -0,0 +1,209 @@
#![allow(dead_code)]
extern crate libc;
use std::mem;
use std::ptr;
#[repr(C)]
#[allow(non_snake_case)]
struct SHA_CTX {
h0: libc::c_uint,
h1: libc::c_uint,
h2: libc::c_uint,
h3: libc::c_uint,
h4: libc::c_uint,
N1: libc::c_uint,
NH: libc::c_uint,
data: [libc::c_uint; 16],
num: libc::c_uint
}
#[repr(C)]
struct RSA;
#[repr(C)]
struct EVP_CIPHER_CTX;
#[repr(C)]
struct EVP_CIPHER;
#[repr(C)]
struct ENGINE;
const RSA_PKCS1_PADDING : libc::c_int = 1;
#[link(name = "crypto")]
extern {
fn SHA1_Init(c: *mut SHA_CTX) -> libc::c_int;
fn SHA1_Update(c: *mut SHA_CTX, data: *const u8, len: libc::size_t) -> libc::c_int;
fn SHA1_Final(md: *const u8, c: *mut SHA_CTX) -> libc::c_int;
fn d2i_RSA_PUBKEY(a: *mut *mut RSA, data: *const *const u8, len: libc::c_int) -> *mut RSA;
fn RSA_free(rsa: *mut RSA);
fn RSA_size(rsa: *mut RSA) -> libc::c_int;
fn RSA_public_encrypt(flen: libc::c_int, from: *const u8, to: *mut u8, rsa: *mut RSA, padding: libc::c_int) -> libc::c_int;
fn RAND_bytes(buf: *mut u8, len: libc::c_int) -> libc::c_int;
fn EVP_CIPHER_CTX_init(a: *mut EVP_CIPHER_CTX);
fn EVP_EncryptInit_ex(ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_CIPHER, engine: *mut ENGINE, key: *const u8, iv: *const u8) -> libc::c_int;
fn EVP_DecryptInit_ex(ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_CIPHER, engine: *mut ENGINE, key: *const u8, iv: *const u8) -> libc::c_int;
fn EVP_EncryptUpdate(ctx: *mut EVP_CIPHER_CTX, out: *mut u8, outl: *mut libc::c_int, input: *const u8, inl: libc::c_int) -> libc::c_int;
fn EVP_DecryptUpdate(ctx: *mut EVP_CIPHER_CTX, out: *mut u8, outl: *mut libc::c_int, input: *const u8, inl: libc::c_int) -> libc::c_int;
fn EVP_aes_128_cfb8() -> *const EVP_CIPHER;
fn EVP_CIPHER_block_size(cipher: *const EVP_CIPHER) -> libc::c_int;
fn EVP_CIPHER_CTX_free(ctx: *mut EVP_CIPHER_CTX);
fn EVP_CIPHER_CTX_cleanup(ctx: *mut EVP_CIPHER_CTX) -> libc::c_int;
fn EVP_CIPHER_CTX_new() -> *mut EVP_CIPHER_CTX;
}
pub struct EVPCipher {
internal: *mut EVP_CIPHER_CTX,
block_size: usize,
}
impl EVPCipher {
pub fn new(key: &Vec<u8>, iv: &Vec<u8>, decrypt: bool) -> EVPCipher {
unsafe {
let e = EVPCipher {
internal: EVP_CIPHER_CTX_new(),
block_size: EVP_CIPHER_block_size(EVP_aes_128_cfb8()) as usize,
};
EVP_CIPHER_CTX_init(e.internal);
if decrypt {
if EVP_DecryptInit_ex(e.internal, EVP_aes_128_cfb8(), ptr::null_mut(), key.as_ptr(), iv.as_ptr()) == 0 {
panic!("Init error");
}
} else {
if EVP_EncryptInit_ex(e.internal, EVP_aes_128_cfb8(), ptr::null_mut(), key.as_ptr(), iv.as_ptr()) == 0 {
panic!("Init error");
}
}
e
}
}
pub fn decrypt(&mut self, data: &[u8]) -> Vec<u8> {
unsafe {
let mut out = Vec::with_capacity(data.len());
let mut out_len: libc::c_int = 0;
if EVP_DecryptUpdate(self.internal, out.as_mut_ptr(), &mut out_len, data.as_ptr(), data.len() as libc::c_int) == 0 {
panic!("Decrypt error")
}
out.set_len(out_len as usize);
out
}
}
pub fn encrypt(&mut self, data: &[u8]) -> Vec<u8> {
unsafe {
let mut out = Vec::with_capacity(data.len());
let mut out_len: libc::c_int = 0;
if EVP_EncryptUpdate(self.internal, out.as_mut_ptr(), &mut out_len, data.as_ptr(), data.len() as libc::c_int) == 0 {
panic!("Encrypt error")
}
out.set_len(out_len as usize);
out
}
}
}
impl Drop for EVPCipher {
fn drop(&mut self) {
unsafe {
EVP_CIPHER_CTX_cleanup(self.internal);
EVP_CIPHER_CTX_free(self.internal);
}
}
}
pub fn gen_random(len: usize) -> Vec<u8> {
unsafe {
let mut data = Vec::with_capacity(len);
data.set_len(len);
RAND_bytes(data.as_mut_ptr(), len as libc::c_int);
data
}
}
pub struct PublicKey {
rsa: *mut RSA
}
impl PublicKey {
pub fn new(data: &Vec<u8>) -> PublicKey {
unsafe {
let cert = d2i_RSA_PUBKEY(ptr::null_mut(), &data.as_ptr(), data.len() as libc::c_int);
if cert.is_null() {
panic!("Error");
}
PublicKey { rsa: cert }
}
}
pub fn encrypt(&mut self, data: &Vec<u8>) -> Vec<u8> {
unsafe {
let size = RSA_size(self.rsa) as usize;
let mut out = Vec::with_capacity(size);
out.set_len(size);
RSA_public_encrypt(data.len() as libc::c_int, data.as_ptr(), out.as_mut_ptr(), self.rsa, RSA_PKCS1_PADDING);
out
}
}
}
impl Drop for PublicKey {
fn drop(&mut self) {
unsafe {
RSA_free(self.rsa);
}
}
}
pub struct SHA1 {
internal: SHA_CTX,
}
impl SHA1 {
pub fn new() -> SHA1 {
unsafe {
let mut s : SHA1 = mem::uninitialized();
if SHA1_Init(&mut s.internal) == 0 {
panic!("error init sha1");
}
s
}
}
pub fn update(&mut self, data: &[u8]) {
unsafe {
if SHA1_Update(&mut self.internal, data.as_ptr(), data.len() as libc::size_t) == 0 {
panic!("sha1 error");
}
}
}
pub fn bytes(mut self) -> Vec<u8> {
unsafe {
let mut dst = Vec::with_capacity(20);
dst.set_len(20);
if SHA1_Final(dst.as_mut_ptr(), &mut self.internal) == 0 {
panic!("sha1 error");
}
dst
}
}
}
#[cfg(test)]
mod test {
use super::{SHA1};
#[test]
fn test_sha1() {
let mut sha1 = SHA1::new();
sha1.update(b"Hello world");
assert!(sha1.bytes().iter().map(|b| format!("{:02X}", b)).collect::<Vec<String>>().connect("") == "7B502C3A1F48C8609AE212CDFB639DEE39673F5E");
}
}

254
src/format.rs Normal file
View File

@ -0,0 +1,254 @@
extern crate serde_json;
use std::fmt;
#[derive(Debug)]
pub enum Component {
Text(TextComponent),
None
}
impl Component {
pub fn from_value(v: &serde_json::Value) -> Self {
let modifier = Modifier::from_value(v);
if let Some(val) = v.as_string() {
Component::Text(TextComponent{text: val.to_owned(), modifier: modifier})
} else if v.find("text").is_some(){
Component::Text(TextComponent::from_value(v, modifier))
} else {
Component::None
}
}
pub fn to_value(&self) -> serde_json::Value {
unimplemented!()
}
}
impl fmt::Display for Component {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Component::Text(ref txt) => write!(f, "{}", txt),
Component::None => Result::Ok(()),
}
}
}
impl Default for Component {
fn default() -> Self {
Component::None
}
}
#[derive(Debug)]
pub struct Modifier {
pub extra: Option<Vec<Component>>,
pub bold: Option<bool>,
pub italic: Option<bool>,
pub underlined: Option<bool>,
pub strikethrough: Option<bool>,
pub obfuscated: Option<bool>,
pub color: Option<Color>,
// click_event
// hover_event
// insertion
}
impl Modifier {
pub fn from_value(v: &serde_json::Value) -> Self {
let mut m = Modifier {
bold: v.find("bold").map_or(Option::None, |v| v.as_boolean()),
italic: v.find("italic").map_or(Option::None, |v| v.as_boolean()),
underlined: v.find("underlined").map_or(Option::None, |v| v.as_boolean()),
strikethrough: v.find("strikethrough").map_or(Option::None, |v| v.as_boolean()),
obfuscated: v.find("obfuscated").map_or(Option::None, |v| v.as_boolean()),
color: v.find("color").map_or(Option::None, |v| v.as_string()).map(|v| Color::from_string(&v.to_owned())),
extra: Option::None,
};
if let Some(extra) = v.find("extra") {
if let Some(data) = extra.as_array() {
let mut ex = Vec::new();
for e in data {
ex.push(Component::from_value(e));
}
m.extra = Some(ex);
}
}
m
}
pub fn to_value(&self) -> serde_json::Value {
unimplemented!()
}
}
#[derive(Debug)]
pub struct TextComponent {
pub text: String,
pub modifier: Modifier,
}
impl TextComponent {
pub fn from_value(v: &serde_json::Value, modifier: Modifier) -> Self {
TextComponent {
text: v.find("text").unwrap().as_string().unwrap_or("").to_owned(),
modifier: modifier,
}
}
pub fn to_value(&self) -> serde_json::Value {
unimplemented!()
}
}
impl fmt::Display for TextComponent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "{}", self.text));
if let Some(ref extra) = self.modifier.extra {
for c in extra {
try!(write!(f, "{}", c));
}
}
Result::Ok(())
}
}
#[derive(Debug)]
pub enum Color {
Black,
DarkBlue,
DarkGreen,
DarkAqua,
DarkRed,
DarkPurple,
Gold,
Gray,
DarkGray,
Blue,
Green,
Aqua,
Red,
LightPurple,
Yellow,
White,
RGB(u8, u8, u8)
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}
impl Color {
fn from_string(val: &String) -> Self {
match val.as_ref() {
"black" => Color::Black,
"dark_blue" => Color::DarkBlue,
"dark_green" => Color::DarkGreen,
"dark_aqua" => Color::DarkAqua,
"dark_red" => Color::DarkRed,
"dark_purple" => Color::DarkPurple,
"gold" => Color::Gold,
"gray" => Color::Gray,
"dark_gray" => Color::DarkGray,
"blue" => Color::Blue,
"green" => Color::Green,
"aqua" => Color::Aqua,
"red" => Color::Red,
"light_purple" => Color::LightPurple,
"yellow" => Color::Yellow,
"white" => Color::White,
val if val.len() == 7 && val.as_bytes()[0] == b'#' => {
let r = match u8::from_str_radix(&val[1..3], 16) {
Ok(r) => r,
Err(_) => return Color::White,
};
let g = match u8::from_str_radix(&val[3..5], 16) {
Ok(g) => g,
Err(_) => return Color::White,
};
let b = match u8::from_str_radix(&val[5..7], 16) {
Ok(b) => b,
Err(_) => return Color::White,
};
Color::RGB(
r,
g,
b
)
}
_ => Color::White,
}
}
fn to_string(&self) -> String {
match *self {
Color::Black => "black".to_owned(),
Color::DarkBlue => "dark_blue".to_owned(),
Color::DarkGreen => "dark_green".to_owned(),
Color::DarkAqua => "dark_aqua".to_owned(),
Color::DarkRed => "dark_red".to_owned(),
Color::DarkPurple => "dark_purple".to_owned(),
Color::Gold => "gold".to_owned(),
Color::Gray => "gray".to_owned(),
Color::DarkGray => "dark_gray".to_owned(),
Color::Blue => "blue".to_owned(),
Color::Green => "green".to_owned(),
Color::Aqua => "aqua".to_owned(),
Color::Red => "red".to_owned(),
Color::LightPurple => "light_purple".to_owned(),
Color::Yellow => "yellow".to_owned(),
Color::White => "white".to_owned(),
Color::RGB(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b),
}
}
#[allow(dead_code)]
fn to_rgb(&self) -> (u8, u8, u8) {
match *self {
Color::Black =>(0, 0, 0),
Color::DarkBlue =>(0, 0, 170),
Color::DarkGreen =>(0, 170, 0),
Color::DarkAqua =>(0, 170, 170),
Color::DarkRed =>(170, 0, 0),
Color::DarkPurple =>(170, 0, 170),
Color::Gold =>(255, 170, 0),
Color::Gray =>(170, 170, 170),
Color::DarkGray =>(85, 85, 85),
Color::Blue =>(85, 85, 255),
Color::Green =>(85, 255, 85),
Color::Aqua =>(85, 255, 255),
Color::Red =>(255, 85, 85),
Color::LightPurple =>(255, 85, 255),
Color::Yellow =>(255, 255, 85),
Color::White =>(255, 255, 255),
Color::RGB(r, g, b) => (r, g, b),
}
}
}
#[test]
fn test_color_from() {
let test = Color::from_string(&"#FF0000".to_owned());
match test {
Color::RGB(r, g, b) => assert!(r == 255 && g == 0 && b == 0),
_ => panic!("Wrong type"),
}
let test = Color::from_string(&"#123456".to_owned());
match test {
Color::RGB(r, g, b) => assert!(r == 0x12 && g == 0x34 && b == 0x56),
_ => panic!("Wrong type"),
}
let test = Color::from_string(&"red".to_owned());
match test {
Color::Red => {},
_ => panic!("Wrong type"),
}
let test = Color::from_string(&"dark_blue".to_owned());
match test {
Color::DarkBlue => {},
_ => panic!("Wrong type"),
}
}

View File

@ -1,8 +1,8 @@
extern crate glfw;
extern crate byteorder;
pub mod bit;
pub mod protocol;
pub mod format;
mod gl;
use glfw::{Action, Context, Key};

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!();
}

65
src/protocol/mojang.rs Normal file
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 =,
}
}
}