Browse Source

Initial commit.

Paul Liétar 9 years ago
commit
a993b60ffa
17 changed files with 872 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 107 0
      Cargo.lock
  3. 28 0
      Cargo.toml
  4. 21 0
      LICENSE
  5. 43 0
      build.rs
  6. 51 0
      protocol/authentication.proto
  7. 61 0
      protocol/keyexchange.proto
  8. 49 0
      protocol/mercury.proto
  9. 154 0
      protocol/metadata.proto
  10. 92 0
      protocol/spirc.proto
  11. 0 0
      protocol/spotify.proto
  12. 43 0
      src/connection.rs
  13. 91 0
      src/cryptoutil.rs
  14. 28 0
      src/main.rs
  15. 3 0
      src/protocol.rs
  16. 78 0
      src/session.rs
  17. 20 0
      src/util.rs

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+target
+Cargo.lock
+.cargo

+ 107 - 0
Cargo.lock

@@ -0,0 +1,107 @@
+[root]
+name = "librespot"
+version = "0.1.0"
+dependencies = [
+ "byteorder 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "protobuf 0.0.9 (git+https://github.com/plietar/rust-protobuf.git)",
+ "protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)",
+ "rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-crypto 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)",
+]
+
+[[package]]
+name = "byteorder"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "gcc"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "log"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mod_path"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "protobuf"
+version = "0.0.9"
+source = "git+https://github.com/plietar/rust-protobuf.git#f26efc36c09602109a01885449e16d15a8494cb8"
+
+[[package]]
+name = "protobuf_macros"
+version = "0.1.0"
+source = "git+https://github.com/plietar/rust-protobuf-macros.git#3631dbaac78e955b36fdb71bb79c9b3cdc4bd4d9"
+
+[[package]]
+name = "rand"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rust-crypto"
+version = "0.2.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rust-gmp"
+version = "0.2.0"
+source = "git+https://github.com/plietar/rust-gmp.git#150cb69f787d413e95860dc7268a6399163f468f"
+
+[[package]]
+name = "rustc-serialize"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "time"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+

+ 28 - 0
Cargo.toml

@@ -0,0 +1,28 @@
+[package]
+name = "librespot"
+version = "0.1.0"
+authors = ["Paul Liétar <paul@lietar.net>"]
+build = "build.rs"
+links = "gmp"
+
+#[[bin]]
+#name = "librespot"
+#path = "src/main.rs"
+
+[dependencies]
+mod_path = "*"
+byteorder = "*"
+num = "*"
+rand = "*"
+lazy_static = "0.1.*"
+rust-crypto = "*"
+
+[dependencies.protobuf]
+git = "https://github.com/plietar/rust-protobuf.git"
+
+[dependencies.protobuf_macros]
+git = "https://github.com/plietar/rust-protobuf-macros.git"
+
+[dependencies.rust-gmp]
+git = "https://github.com/plietar/rust-gmp.git"
+

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Paul Lietar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 43 - 0
build.rs

@@ -0,0 +1,43 @@
+use std::env;
+use std::process::{Command, Stdio};
+use std::path::Path;
+
+#[derive(Debug)]
+enum ProtobufError {
+    IoError(::std::io::Error),
+    Other
+}
+
+impl std::convert::From<::std::io::Error> for ProtobufError {
+    fn from(e: ::std::io::Error) -> ProtobufError {
+        ProtobufError::IoError(e)
+    }
+}
+
+fn compile(prefix : &Path, files : &[&Path]) -> Result<(),ProtobufError>{
+    let mut c = Command::new("protoc");
+    c.arg("--rust_out").arg(env::var("OUT_DIR").unwrap())
+        .arg("--proto_path").arg(prefix.to_str().unwrap());
+    for f in files.iter() {
+        c.arg(f.to_str().unwrap());
+    }
+
+    //c.stdout(Stdio::inherit());
+    c.stderr(Stdio::inherit());
+
+    let mut p = try!(c.spawn());
+    let r = try!(p.wait());
+    return match r.success() {
+        true => Ok(()),
+        false => Err(ProtobufError::Other),
+    };
+}
+
+fn main() {
+    let prefix = Path::new("protocol");
+    compile(&prefix, &[
+            &prefix.join("keyexchange.proto"),
+            &prefix.join("authentication.proto")
+    ]).unwrap();
+}
+

+ 51 - 0
protocol/authentication.proto

@@ -0,0 +1,51 @@
+message AuthRequest {
+    enum LoginMethod {
+        PASSWORD = 0x0;
+        TOKEN = 0x3;
+    }
+
+    message Credentials {
+        optional string username = 0x0a;
+        required LoginMethod method = 0x14;
+        required bytes  password = 0x1e;
+    }
+    required Credentials credentials = 0x0a;
+
+    message Data1 {
+        required uint32 data0 = 0x0a;
+        required uint32 data1 = 0x3c;
+        required string partner = 0x5a; // "Partner %s %s;%s" % ("lenbrook_bluesound", brand, model)
+        required string deviceid = 0x64; // sha1(os_device_id).hexdigest()
+    }
+    required Data1 data1 = 0x32;
+
+    required string version = 0x46;
+
+    message Data3 {
+        required uint32 data0 = 0x01;
+        required bytes  appkey1 = 0x02;
+        required bytes  appkey2 = 0x03;
+        required string data3 = 0x04;
+        required bytes  data4 = 0x05;
+    }
+    required Data3 data3 = 0x50;
+}
+
+message AuthSuccess {
+    required string username = 0x0a;
+    required uint32 data1 = 0x14;
+    required uint32 data2 = 0x19;
+    required uint32 data3 = 0x1e;
+    required bytes  data4 = 0x28;
+    required bytes  data5 = 0x32;
+}
+
+message AuthFailure {
+    required uint32 code = 0x0a;
+    required Data1 data1 = 0x32;
+
+    message Data1 {
+        required string data0 = 0x01;
+    }
+}
+

+ 61 - 0
protocol/keyexchange.proto

@@ -0,0 +1,61 @@
+message Request {
+    message Data0 {
+        required uint32 data0 = 0x0a; // 05
+        required uint32 data1 = 0x1e; // 02
+        required uint64 data2 = 0x28; // 10800000000
+    }
+
+    message Data2 {
+        message Data0 {
+            required bytes data0 = 0x0a; // 60
+            required uint32 data1 = 0x14; // 01
+        }
+        required Data0 data0 = 0x0a; // 65
+    }
+
+    required Data0 data0 = 0x0a; // 0d
+    required uint32 data1 = 0x1e; // 00
+    required Data2 data2 = 0x32; // 67
+    required bytes random = 0x3c; // 10
+    required bytes data4 = 0x46; // 01
+    required bytes data5 = 0x50; // 02
+
+}
+
+message Response {
+    message Data {
+        message Data0 {
+            message Data0 {
+                required bytes data0 = 0x0a; // 60
+                required uint32 data1 = 0x14;
+                required bytes data2 = 0x1e; // 100
+            }
+            required Data0 data0 = 0x0a;
+        }
+        message Data3 {
+            required bytes data0 = 0x0a;
+        }
+
+        required Data0 data0 = 0x0a;
+        required bytes data1 = 0x14;
+        required bytes data2 = 0x1e;
+        required Data3 data3 = 0x28;
+        required bytes data4 = 0x32;
+        required bytes data5 = 0x3c;
+    }
+
+    required Data data = 0x0a;
+}
+
+message ChallengePacket {
+    message Data0 {
+        message Data0 {
+            required bytes data0 = 0x0a;
+        }
+        required Data0 data0 = 0x0a;
+    }
+    required Data0 data0 = 0x0a;
+    required bytes data1 = 0x14;
+    required bytes data2 = 0x1e;
+}
+

+ 49 - 0
protocol/mercury.proto

@@ -0,0 +1,49 @@
+message MercuryRequest {
+    required string url = 0x01;
+    optional string mime = 0x02;
+    required string method = 0x03;
+}
+
+message MercuryReply {
+    required string url = 0x01;
+    required string mime = 0x02;
+    required sint32 code = 0x04;
+    repeated Header header = 0x06;
+
+    message Header {
+        required string key = 0x01;
+        required bytes value = 0x02;
+    }
+}
+
+message MercuryGetRequest {
+    required string url = 0x01;
+    optional string method = 0x03;
+}
+
+message MercuryMultiGetRequest {
+    repeated MercuryGetRequest request = 0x01;
+}
+
+message MercuryGetReply {
+    enum CachePolicy {
+        CACHE_NO = 1;
+        CACHE_PRIVATE = 2;
+        CACHE_PUBLIC = 3;
+    }
+    optional sint32 code = 0x01;
+    optional CachePolicy cache_policy = 0x03;
+    optional uint32 ttl = 0x04;
+    optional bytes etag = 0x05;
+    optional string mime = 0x06;
+    optional bytes body = 0x07;
+}
+
+message MercuryMultiGetReply {
+    repeated MercuryGetReply reply = 0x1;
+}
+
+message MercurySubscribed {
+    required string url = 0x1;
+}
+

+ 154 - 0
protocol/metadata.proto

@@ -0,0 +1,154 @@
+message TopTracks {
+    optional string country = 1;
+    repeated Track track = 2;
+}
+message ActivityPeriod {
+    optional sint32 start_year = 1;
+    optional sint32 end_year = 2;
+    optional sint32 decade = 3;
+}
+message Artist {
+    optional bytes gid = 1;
+    optional string name = 2;
+    optional sint32 popularity = 3;
+    repeated TopTracks top_track = 4;
+    repeated AlbumGroup album_group = 5;
+    repeated AlbumGroup single_group = 6;
+    repeated AlbumGroup compilation_group = 7;
+    repeated AlbumGroup appears_on_group = 8;
+    repeated string genre = 9;
+    repeated ExternalId external_id = 10;
+    repeated Image portrait = 11;
+    repeated Biography biography = 12;
+    repeated ActivityPeriod activity_period = 13;
+    repeated Restriction restriction = 14;
+    repeated Artist related = 15;
+    optional bool is_portrait_album_cover = 16;
+    optional ImageGroup portrait_group = 17;
+}
+message AlbumGroup {
+    repeated Album album = 1;
+}
+message Date {
+    optional sint32 year = 1;
+    optional sint32 month = 2;
+    optional sint32 day = 3;
+}
+message Album {
+    enum Type {
+        ALBUM = 1;
+        SINGLE = 2;
+        COMPILATION = 3;
+    }
+    optional bytes gid = 1;
+    optional string name = 2;
+    repeated Artist artist = 3;
+    optional Type type = 4;
+    optional string label = 5;
+    optional Date date = 6;
+    optional sint32 popularity = 7;
+    repeated string genre = 8;
+    repeated Image cover = 9;
+    repeated ExternalId external_id = 10;
+    repeated Disc disc = 11;
+    repeated string review = 12;
+    repeated Copyright copyright = 13;
+    repeated Restriction restriction = 14;
+    repeated Album related = 15;
+    repeated SalePeriod sale_period  = 16;
+    optional ImageGroup cover_group  = 17;
+}
+
+message Track {
+    optional bytes gid = 1;
+    optional string name = 2;
+    optional Album album = 3;
+    repeated Artist artist = 4;
+    optional sint32 number = 5;
+    optional sint32 disc_number = 6;
+    optional sint32 duration = 7;
+    optional sint32 popularity = 8;
+    optional bool explicit = 9;
+    repeated ExternalId external_id = 10;
+    repeated Restriction restriction = 11;
+    repeated AudioFile file = 12;
+    repeated Track alternative = 13;
+    repeated SalePeriod sale_period = 14;
+    repeated AudioFile preview = 15;
+}
+message Image {
+    enum Size {
+        DEFAULT = 0;
+        SMALL = 1;
+        LARGE = 2;
+        XLARGE = 3;
+    }
+    optional bytes file_id = 1;
+    optional Size size = 2;
+    optional sint32 width = 3;
+    optional sint32 height = 4;
+}
+message ImageGroup {
+    repeated Image image = 1;
+}
+message Biography {
+    optional string text = 1;
+    repeated Image portrait = 2;
+    repeated ImageGroup portrait_group = 3;
+}
+message Disc {
+    optional sint32 number = 1;
+    optional string name = 2;
+    repeated Track track = 3;
+}
+message Copyright {
+    enum Type {
+        P = 0;
+        C = 1;
+    }
+    optional Type type = 1;
+    optional string text = 2;
+}
+message Restriction {
+    enum Catalogue {
+        FREE = 0;
+        PREMIUM = 1;
+        SHUFFLE = 3;
+        COMMERCIAL = 4;
+    }
+    enum Type {
+        STREAMING = 0;
+    }
+    repeated Catalogue catalogue = 1;
+    optional string countries_allowed = 2;
+    optional string countries_forbidden = 3;
+    optional Type type = 4;
+    repeated string usage = 5;
+}
+
+message SalePeriod {
+    repeated Restriction restriction = 1;
+    optional Date start = 2;
+    optional Date end = 3;
+}
+
+message ExternalId {
+    optional string type = 1;
+    optional string id = 2;
+}
+
+message AudioFile {
+    enum Format {
+        OGG_VORBIS_96 = 0;
+        OGG_VORBIS_160 = 1;
+        OGG_VORBIS_320 = 2;
+        MP3_256 = 3;
+        MP3_320 = 4;
+        MP3_160 = 5;
+        MP3_96 = 6;
+        OTHER1 = 7; // TODO
+        OTHER2 = 8; // TODO
+    }
+    optional bytes gid = 1;
+    optional Format format = 2;
+}

+ 92 - 0
protocol/spirc.proto

@@ -0,0 +1,92 @@
+enum MessageType {
+    kMessageTypeHello = 1;
+    kMessageTypeGoodbye = 2;
+    kMessageTypeNotify = 10;
+    kMessageTypeLoad = 20;
+    kMessageTypePlay = 21;
+    kMessageTypePause = 22;
+//    kMessageTypePlayPause = 23;
+    kMessageTypeSeek = 24;
+    kMessageTypePrev = 25;
+    kMessageTypeNext = 26;
+    kMessageTypeVolume = 27;
+    kMessageTypeShuffle = 28;
+    kMessageTypeRepeat = 29;
+    kMessageTypeQueue = 30;
+    kMessageTypeVolumeDown = 31;
+    kMessageTypeVolumeUp = 32;
+    kMessageTypeAddToQueue = 33;
+}
+enum PlayStatus {
+    kPlayStatusStop = 0;
+    kPlayStatusPlay = 1;
+    kPlayStatusPause = 2;
+    kPlayStatusLoading = 3;
+    kPlayStatusError = 4;
+}
+message Goodbye {
+    required string reason = 1;
+}
+message State {
+    optional string contextURI = 0x2;
+    optional uint32 index = 0x3;
+    optional uint32 position = 0x4;
+    optional PlayStatus status = 0x5;
+
+    optional uint64 timestamp = 0x7;
+    optional string context_name = 0x8;
+    optional uint32 duration = 0x9;
+    optional uint32 data9 = 0xa;
+    repeated uint64 data10 = 0xb;
+    optional bool shuffle = 0xd;
+    optional bool repeat = 0xe;
+
+    optional string data12 = 0x14;
+    optional uint32 data13 = 0x15;
+    optional uint32 data14 = 0x18;
+    optional uint32 data15 = 0x19;
+    optional uint32 data16 = 0x1a;
+    repeated QueuedTrack queued = 0x1b;
+    message QueuedTrack {
+        optional bytes  gid = 0x1;
+        optional string local_uri = 0x2;
+        optional uint32 data1 = 0x3;
+    }
+}
+
+message Frame {
+    required uint32 version = 1;
+    required string source = 2;
+    required string version_string = 3;
+    required uint32 msgid = 4;
+    required uint32 type = 5;
+
+    required DeviceInfo device = 0x7;
+
+    //required Goodbye goodbye = 0xb;
+    optional State state = 0xc;
+
+    optional uint32 position = 0xd;
+    optional uint32 volume = 0xe;
+
+    optional uint64 timestamp = 0x11;
+    optional string destination = 0x12;
+
+    message DeviceInfo {
+        optional string version    = 0x1;
+        required bool   active     = 0xa;
+        required bool   foreground = 0xb;
+        required uint32 volume     = 0xc;
+        required string name       = 0xd;
+        optional uint32 data15     = 0xe;
+        required uint64 activeTime = 0xf;
+        repeated Data17 data17     = 0x11;
+
+        message Data17 {
+            required uint32 data0 = 0x1;
+            optional uint32 data1 = 0x2;
+            repeated string data2 = 0x3;
+        }
+    }
+}
+

+ 0 - 0
protocol/spotify.proto


+ 43 - 0
src/connection.rs

@@ -0,0 +1,43 @@
+use util;
+
+use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
+use std::io::{Write,Read};
+use std::net::TcpStream;
+
+pub struct Connection {
+    stream: TcpStream,
+}
+
+impl Connection {
+    pub fn connect() -> Connection {
+        Connection {
+            stream: TcpStream::connect("lon3-accesspoint-a26.ap.spotify.com:4070").unwrap(),
+        }
+    }
+
+    pub fn send_packet(&mut self, data: &[u8]) -> Vec<u8> {
+        self.send_packet_prefix(&[], data)
+    }
+
+    pub fn send_packet_prefix(&mut self, prefix: &[u8], data: &[u8]) -> Vec<u8> {
+        let size = prefix.len() + 4 + data.len();
+        let mut buf = Vec::with_capacity(size);
+
+        buf.write(prefix).unwrap();
+        buf.write_u32::<BigEndian>(size as u32).unwrap();
+        buf.write(data).unwrap();
+        self.stream.write(&buf).unwrap();
+
+        buf
+    }
+
+    pub fn recv_packet(&mut self) -> Vec<u8> {
+        let size : usize = self.stream.read_u32::<BigEndian>().unwrap() as usize;
+        let mut buffer = util::alloc_buffer(size - 4);
+
+        self.stream.read(&mut buffer).unwrap();
+
+        buffer
+    }
+}
+

+ 91 - 0
src/cryptoutil.rs

@@ -0,0 +1,91 @@
+use rand;
+use gmp::Mpz;
+use std::num::FromPrimitive;
+use crypto;
+use crypto::mac::Mac;
+use std::io::Write;
+
+use util;
+
+lazy_static! {
+    static ref DH_GENERATOR: Mpz = Mpz::from_u64(0x2).unwrap();
+    static ref DH_PRIME: Mpz = Mpz::from_bytes_be(&[
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
+        0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
+        0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e,
+        0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
+        0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
+        0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a,
+        0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14,
+        0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
+        0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4,
+        0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]);
+}
+
+pub struct Crypto {
+    private_key: Mpz,
+    public_key: Mpz,
+    shared: Option<SharedKeys>,
+}
+
+pub struct SharedKeys {
+    pub challenge: Vec<u8>,
+    pub send_key: Vec<u8>,
+    pub recv_key: Vec<u8>
+}
+
+impl Crypto {
+    pub fn new() -> Crypto {
+        let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
+        Self::new_with_key(&key_data)
+    }
+
+    pub fn new_with_key(key_data: &[u8]) -> Crypto {
+        let private_key = Mpz::from_bytes_be(key_data);
+        let public_key = DH_GENERATOR.powm(&private_key, &DH_PRIME);
+
+        Crypto {
+            private_key: private_key,
+            public_key: public_key,
+            shared: None,
+        }
+    }
+
+    pub fn setup(&mut self, remote_key: &[u8], client_packet: &[u8], server_packet: &[u8]) {
+        let shared_key = Mpz::from_bytes_be(remote_key).powm(&self.private_key, &DH_PRIME);
+
+        let mut data = Vec::with_capacity(0x54);
+        let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &shared_key.to_bytes_be());
+
+        for i in 1..6 {
+            h.input(client_packet);
+            h.input(server_packet);
+            h.input(&[i]);
+            data.write(&h.result().code()).unwrap();
+            h.reset();
+        }
+
+        h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &data[..0x14]);
+        h.input(client_packet);
+        h.input(server_packet);
+
+        self.shared = Some(SharedKeys{
+            challenge: h.result().code().to_vec(),
+            send_key: data[0x14..0x34].to_vec(),
+            recv_key: data[0x34..0x54].to_vec()
+        });
+    }
+
+    pub fn public_key(&self) -> Vec<u8> {
+        return self.public_key.to_bytes_be();
+    }
+
+    pub fn shared(&self) -> &SharedKeys {
+        match self.shared {
+            Some(ref shared) => shared,
+            None => panic!("ABC")
+        }
+    }
+}
+

+ 28 - 0
src/main.rs

@@ -0,0 +1,28 @@
+#![crate_name = "librespot"]
+
+#![feature(plugin,core)]
+
+#![plugin(mod_path)]
+#![plugin(protobuf_macros)]
+#[macro_use] extern crate lazy_static;
+
+extern crate byteorder;
+extern crate crypto;
+extern crate gmp;
+extern crate num;
+extern crate protobuf;
+extern crate rand;
+
+mod connection;
+mod cryptoutil;
+mod protocol;
+mod session;
+mod util;
+
+use session::Session;
+
+fn main() {
+    let mut s = Session::new();
+    s.login();
+}
+

+ 3 - 0
src/protocol.rs

@@ -0,0 +1,3 @@
+mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
+mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));
+

+ 78 - 0
src/session.rs

@@ -0,0 +1,78 @@
+use connection::Connection;
+use cryptoutil::Crypto;
+use protocol;
+use util;
+use std::iter::{FromIterator,repeat};
+
+use protobuf::*;
+use rand::thread_rng;
+
+pub struct Session {
+    connection: Connection,
+    crypto: Crypto,
+}
+
+impl Session {
+    pub fn new() -> Session {
+        Session {
+            connection: Connection::connect(),
+            crypto: Crypto::new(),
+        }
+    }
+
+    pub fn login(&mut self) {
+        let request = protobuf_init!(protocol::keyexchange::Request::new(), {
+            data0 => {
+                data0: 0x05,
+                data1: 0x01,
+                data2: 0x10800000000,
+            },
+            data1: 0,
+            data2.data0 => {
+                data0: self.crypto.public_key(),
+                data1: 1,
+            },
+            random: util::rand_vec(&mut thread_rng(), 0x10),
+            data4: vec![0x1e],
+            data5: vec![0x08, 0x01]
+        });
+
+        let init_client_packet =
+            self.connection.send_packet_prefix(&[0,4], &request.write_to_bytes().unwrap());
+        let init_server_packet =
+            self.connection.recv_packet();
+
+        let response : protocol::keyexchange::Response =
+            parse_from_bytes(&init_server_packet).unwrap();
+
+        protobuf_bind!(response, { data.data0.data0.data0: remote_key });
+
+        self.crypto.setup(&remote_key, &init_client_packet, &init_server_packet);
+
+        return;
+        let appkey = vec![];
+        let request = protobuf_init!(protocol::authentication::AuthRequest::new(), {
+            credentials => {
+                username: "USERNAME".to_string(),
+                method: protocol::authentication::AuthRequest_LoginMethod::PASSWORD,
+                password: b"PASSWORD".to_vec(),
+            },
+            data1 => {
+                data0: 0,
+                data1: 0,
+                partner: "Partner blabla".to_string(),
+                deviceid: "abc".to_string()
+            },
+            version: "master-v1.8.0-gad9e5b46".to_string(),
+            data3 => {
+                data0: 1,
+                appkey1: appkey[0x1..0x81].to_vec(),
+                appkey2: appkey[0x81..0x141].to_vec(),
+                data3: "".to_string(),
+                data4: Vec::from_iter(repeat(0).take(20))
+            }
+        });
+        //println!("{:?}", response);
+    }
+}
+

+ 20 - 0
src/util.rs

@@ -0,0 +1,20 @@
+use rand::{Rng,Rand};
+
+pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
+    let mut vec = Vec::with_capacity(size);
+
+    for _ in 0..size {
+        vec.push(R::rand(rng));
+    }
+
+    return vec
+}
+
+pub fn alloc_buffer(size: usize) -> Vec<u8> {
+    let mut vec = Vec::with_capacity(size);
+    unsafe {
+        vec.set_len(size);
+    }
+
+    vec
+}