Parcourir la source

Lots of stuff

Paul Lietar il y a 9 ans
Parent
commit
7ffe996652

+ 109 - 29
Cargo.lock

@@ -2,12 +2,13 @@
 name = "librespot"
 version = "0.1.0"
 dependencies = [
- "byteorder 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "librespot-protocol 0.1.0",
  "mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "protobuf 0.0.10 (git+https://github.com/stepancheg/rust-protobuf.git)",
+ "num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "portaudio 0.1.2 (git+https://github.com/mvdnes/portaudio-rs)",
+ "protobuf 1.0.0 (git+https://github.com/stepancheg/rust-protobuf.git)",
  "protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)",
  "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "readall 0.1.0 (git+https://github.com/plietar/rust-readall.git)",
@@ -15,6 +16,7 @@ dependencies = [
  "rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)",
  "shannon 0.1.0 (git+https://github.com/plietar/rust-shannon.git)",
  "vergen 0.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vorbis 0.0.11 (git+https://github.com/plietar/vorbis-rs)",
 ]
 
 [[package]]
@@ -22,24 +24,29 @@ name = "bitflags"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "bitflags"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "byteorder"
-version = "0.3.9"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "gcc"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "lazy_static"
-version = "0.1.10"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "libc"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -47,7 +54,7 @@ name = "librespot-protocol"
 version = "0.1.0"
 dependencies = [
  "mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "protobuf 0.0.10 (git+https://github.com/stepancheg/rust-protobuf.git)",
+ "protobuf 1.0.0 (git+https://github.com/stepancheg/rust-protobuf.git)",
 ]
 
 [[package]]
@@ -57,29 +64,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "num"
-version = "0.1.24"
+version = "0.1.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ogg-sys"
+version = "0.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "portaudio"
+version = "0.1.2"
+source = "git+https://github.com/mvdnes/portaudio-rs#a6432fb11acebb5a2d9997fc0019eeb482ba435d"
+dependencies = [
+ "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "portaudio_sys 0.1.0 (git+https://github.com/mvdnes/portaudio-rs)",
+]
+
+[[package]]
+name = "portaudio_sys"
+version = "0.1.0"
+source = "git+https://github.com/mvdnes/portaudio-rs#a6432fb11acebb5a2d9997fc0019eeb482ba435d"
+dependencies = [
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "protobuf"
-version = "0.0.10"
-source = "git+https://github.com/stepancheg/rust-protobuf.git#41fde39aed305e0fb71ef6a8d92b35ee50550bde"
+version = "1.0.0"
+source = "git+https://github.com/stepancheg/rust-protobuf.git#d6e80593f38ce47dfa0c4912a3558fa33ee06143"
 
 [[package]]
 name = "protobuf_macros"
 version = "0.1.0"
-source = "git+https://github.com/plietar/rust-protobuf-macros.git#e95dbc5bdf6c13787e2385d66d9d003afcaf9f17"
+source = "git+https://github.com/plietar/rust-protobuf-macros.git#5fa976178a48b01bdf2da6d5e7929367e348ea04"
 
 [[package]]
 name = "rand"
 version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -92,46 +133,51 @@ name = "rust-crypto"
 version = "0.2.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.26 (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#eaf298870d63712d18f8fab6bbbf0cb1e14dbb7f"
+source = "git+https://github.com/plietar/rust-gmp.git#db2bb627165b12ebe18a41a941ac6284ce9b895d"
+dependencies = [
+ "num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "rustc-serialize"
-version = "0.3.14"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "shannon"
 version = "0.1.0"
-source = "git+https://github.com/plietar/rust-shannon.git#83a49c3397e1e546e6079cf54a0e5b2f85c6b13f"
+source = "git+https://github.com/plietar/rust-shannon.git#c6be8a879a523a77d81c50df46faa891b76fea25"
 dependencies = [
+ "byteorder 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "readall 0.1.0 (git+https://github.com/plietar/rust-readall.git)",
  "shannon-sys 0.1.0 (git+https://github.com/plietar/rust-shannon.git)",
 ]
 
 [[package]]
 name = "shannon-sys"
 version = "0.1.0"
-source = "git+https://github.com/plietar/rust-shannon.git#83a49c3397e1e546e6079cf54a0e5b2f85c6b13f"
+source = "git+https://github.com/plietar/rust-shannon.git#c6be8a879a523a77d81c50df46faa891b76fea25"
 dependencies = [
- "gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "time"
-version = "0.1.25"
+version = "0.1.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -139,7 +185,41 @@ name = "vergen"
 version = "0.0.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "vorbis"
+version = "0.0.11"
+source = "git+https://github.com/plietar/vorbis-rs#cff6b4222cebd0fb31bcbc2e14a7ba575548c703"
+dependencies = [
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ogg-sys 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vorbis-sys 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vorbisfile-sys 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "vorbis-sys"
+version = "0.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ogg-sys 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "vorbisfile-sys"
+version = "0.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ogg-sys 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vorbis-sys 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 

+ 4 - 4
Cargo.toml

@@ -17,18 +17,18 @@ rust-crypto = "*"
 
 [dependencies.protobuf]
 git = "https://github.com/stepancheg/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"
-
 [dependencies.shannon]
 git = "https://github.com/plietar/rust-shannon.git"
-
 [dependencies.readall]
 git = "https://github.com/plietar/rust-readall.git"
+[dependencies.portaudio]
+git = "https://github.com/mvdnes/portaudio-rs"
+[dependencies.vorbis]
+git = "https://github.com/plietar/vorbis-rs"
 
 [build-dependencies]
 vergen = "*"

+ 10 - 1
protocol/build.rs

@@ -37,9 +37,18 @@ fn compile(prefix : &Path, files : &[&Path]) -> Result<(),ProtobufError>{
 fn main() {
     let root = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap());
     let proto = root.join("proto");
+
     compile(&proto, &[
             &proto.join("keyexchange.proto"),
-            &proto.join("authentication.proto")
+            &proto.join("authentication.proto"),
+            &proto.join("mercury.proto"),
+            &proto.join("metadata.proto"),
+            &proto.join("playlist4changes.proto"),
+            &proto.join("playlist4content.proto"),
+            &proto.join("playlist4issues.proto"),
+            &proto.join("playlist4meta.proto"),
+            &proto.join("playlist4ops.proto"),
+            &proto.join("playlist4service.proto"),
     ]).unwrap();
 }
 

+ 4 - 4
protocol/proto/metadata.proto

@@ -43,7 +43,7 @@ message Album {
     optional bytes gid = 1;
     optional string name = 2;
     repeated Artist artist = 3;
-    optional Type type = 4;
+    optional Type typ = 4;
     optional string label = 5;
     optional Date date = 6;
     optional sint32 popularity = 7;
@@ -106,7 +106,7 @@ message Copyright {
         P = 0;
         C = 1;
     }
-    optional Type type = 1;
+    optional Type typ = 1;
     optional string text = 2;
 }
 message Restriction {
@@ -122,7 +122,7 @@ message Restriction {
     repeated Catalogue catalogue = 1;
     optional string countries_allowed = 2;
     optional string countries_forbidden = 3;
-    optional Type type = 4;
+    optional Type typ = 4;
     repeated string usage = 5;
 }
 
@@ -133,7 +133,7 @@ message SalePeriod {
 }
 
 message ExternalId {
-    optional string type = 1;
+    optional string typ = 1;
     optional string id = 2;
 }
 

+ 80 - 0
protocol/proto/playlist4changes.proto

@@ -0,0 +1,80 @@
+import "playlist4content.proto";
+import "playlist4issues.proto";
+import "playlist4meta.proto";
+import "playlist4ops.proto";
+
+message ChangeInfo {
+  optional string user = 1;
+  optional int32 timestamp = 2;
+  optional bool admin = 3;
+  optional bool undo = 4;
+  optional bool redo = 5;
+  optional bool merge = 6;
+  optional bool compressed = 7;
+  optional bool migration = 8;
+}
+message Delta {
+  optional bytes base_version = 1;
+  repeated Op ops = 2;
+  optional ChangeInfo info = 4;
+}
+message Merge {
+  optional bytes base_version = 1;
+  optional bytes merge_version = 2;
+  optional ChangeInfo info = 4;
+}
+message ChangeSet {
+  enum Kind {
+    KIND_UNKNOWN = 0;
+    DELTA = 2;
+    MERGE = 3;
+  };
+  required Kind kind = 1;
+  optional Delta delta = 2;
+  optional Merge merge = 3;
+}
+message RevisionTaggedChangeSet {
+  required bytes revision = 1;
+  required ChangeSet change_set = 2;
+}
+message Diff {
+  required bytes from_revision = 1;
+  repeated Op ops = 2;
+  required bytes to_revision = 3;
+}
+message ListDump {
+  optional bytes latestRevision = 1;
+  optional int32 length = 2;
+  optional ListAttributes attributes = 3;
+  optional ListChecksum checksum = 4;
+  optional ListItems contents = 5;
+  repeated Delta pendingDeltas = 7;
+}
+message ListChanges {
+  optional bytes baseRevision = 1;
+  repeated Delta deltas = 2;
+  optional bool wantResultingRevisions = 3;
+  optional bool wantSyncResult = 4;
+  optional ListDump dump = 5;
+  repeated int32 nonces = 6;
+}
+message SelectedListContent {
+  optional bytes revision = 1;
+  optional int32 length = 2;
+  optional ListAttributes attributes = 3;
+  optional ListChecksum checksum = 4;
+  optional ListItems contents = 5;
+  optional Diff diff = 6;
+
+  optional Diff syncResult = 7;
+  repeated bytes resultingRevisions = 8;
+
+  optional bool multipleHeads = 9;
+
+  optional bool upToDate = 10;
+
+  repeated ClientResolveAction resolveAction = 12;
+  repeated ClientIssue issues = 13;
+
+  repeated int32 nonces = 14;
+}

+ 31 - 0
protocol/proto/playlist4content.proto

@@ -0,0 +1,31 @@
+import "playlist4meta.proto";
+import "playlist4issues.proto";
+
+message Item {
+  required string uri = 1;
+  optional ItemAttributes attributes = 2;
+}
+message ListItems {
+  required int32 pos = 1;
+  required bool truncated = 2;
+  repeated Item items = 3;
+}
+message ContentRange {
+  required int32 pos = 1;
+  optional int32 length = 2;
+}
+message ListContentSelection {
+  optional bool wantRevision = 1;
+  optional bool wantLength = 2;
+  optional bool wantAttributes = 3;
+  optional bool wantChecksum = 4;
+  optional bool wantContent = 5;
+  optional ContentRange contentRange = 6;
+  optional bool wantDiff = 7;
+  optional bytes baseRevision = 8;
+  optional bytes hintRevision = 9;
+  optional bool wantNothingIfUpToDate = 10;
+  optional bool wantResolveAction = 12;
+  repeated ClientIssue issues = 13;
+  repeated ClientResolveAction resolveAction = 14;
+}

+ 40 - 0
protocol/proto/playlist4issues.proto

@@ -0,0 +1,40 @@
+message ClientIssue {
+  enum Level {
+    LEVEL_UNKNOWN = 0;
+    LEVEL_DEBUG = 1;
+    LEVEL_INFO = 2;
+    LEVEL_NOTICE = 3;
+    LEVEL_WARNING = 4;
+    LEVEL_ERROR = 5;
+  }
+  enum Code {
+    CODE_UNKNOWN = 0;
+    CODE_INDEX_OUT_OF_BOUNDS = 1;
+    CODE_VERSION_MISMATCH = 2;
+    CODE_CACHED_CHANGE = 3;
+    CODE_OFFLINE_CHANGE = 4;
+    CODE_CONCURRENT_CHANGE = 5;
+  }
+  optional Level level = 1;
+  optional Code code = 2;
+  optional int32 repeatCount = 3;
+}
+
+message ClientResolveAction {
+  enum Code {
+    CODE_UNKNOWN = 0;
+    CODE_NO_ACTION = 1;
+    CODE_RETRY = 2;
+    CODE_RELOAD = 3;
+    CODE_DISCARD_LOCAL_CHANGES = 4;
+    CODE_SEND_DUMP = 5;
+    CODE_DISPLAY_ERROR_MESSAGE = 6;
+  }
+  enum Initiator {
+    INITIATOR_UNKNOWN = 0;
+    INITIATOR_SERVER = 1;
+    INITIATOR_CLIENT = 2;
+  }
+  optional Code code = 1;
+  optional Initiator initiator = 2;
+}

+ 58 - 0
protocol/proto/playlist4meta.proto

@@ -0,0 +1,58 @@
+message ListChecksum {
+  required int32 version = 1;
+  optional bytes sha1 = 4;
+}
+message DownloadFormat {
+  enum Codec {
+    CODEC_UNKNOWN = 0;
+    OGG_VORBIS = 1;
+    FLAC = 2;
+    MPEG_1_LAYER_3 = 3;
+  }
+  required Codec codec = 1;
+}
+enum ListAttributeKind {
+  LIST_UNKNOWN = 0;
+  LIST_NAME = 1;
+  LIST_DESCRIPTION = 2;
+  LIST_PICTURE = 3;
+  LIST_COLLABORATIVE = 4;
+  LIST_PL3_VERSION = 5;
+  LIST_DELETED_BY_OWNER = 6;
+  LIST_RESTRICTED_COLLABORATIVE = 7;
+}
+message ListAttributes {
+  optional string name = 1;
+  optional string description = 2;
+  optional bytes picture = 3;
+  optional bool collaborative = 4;
+  optional string pl3_version = 5;
+  optional bool deleted_by_owner = 6;
+  optional bool restricted_collaborative = 7;
+}
+enum ItemAttributeKind {
+  ITEM_UNKNOWN = 0;
+  ITEM_ADDED_BY = 1;
+  ITEM_TIMESTAMP = 2;
+  ITEM_MESSAGE = 3;
+  ITEM_SEEN = 4;
+  ITEM_DOWNLOAD_COUNT = 5;
+  ITEM_DOWNLOAD_FORMAT = 6;
+  ITEM_SEVENDIGITAL_ID = 7;
+  ITEM_SEVENDIGITAL_LEFT = 8;
+  ITEM_SEEN_AT = 9;
+}
+message ItemAttributes {
+  optional string added_by = 1;
+  optional string message = 3;
+  optional bool seen = 4;
+  optional DownloadFormat download_format = 6;
+  optional string sevendigital_id = 7;
+}
+message StringAttribute {
+  required string key = 1;
+  required string value = 2;
+}
+message StringAttributes {
+  repeated StringAttribute attribute = 1;
+}

+ 68 - 0
protocol/proto/playlist4ops.proto

@@ -0,0 +1,68 @@
+import "playlist4content.proto";
+import "playlist4meta.proto";
+
+message Add {
+  optional int32 fromIndex = 1;
+  repeated Item items = 2;
+  optional ListChecksum list_checksum = 3;
+  optional bool addLast = 4;
+  optional bool addFirst = 5;
+}
+message Rem {
+  optional int32 fromIndex = 1;
+  optional int32 length = 2;
+  repeated Item items = 3;
+  optional ListChecksum list_checksum = 4;
+  optional ListChecksum items_checksum = 5;
+  optional ListChecksum uris_checksum = 6;
+  optional bool itemsAsKey = 7;
+}
+message Mov {
+  required int32 fromIndex = 1;
+  required int32 length = 2;
+  required int32 toIndex = 3;
+  optional ListChecksum list_checksum = 4;
+  optional ListChecksum items_checksum = 5;
+  optional ListChecksum uris_checksum = 6;
+}
+message ItemAttributesPartialState {
+  required ItemAttributes values = 1;
+  repeated ItemAttributeKind no_value = 2;
+}
+message ListAttributesPartialState {
+  required ListAttributes values = 1;
+  repeated ListAttributeKind no_value = 2;
+}
+message UpdateItemAttributes {
+  required int32 index = 1;
+  required ItemAttributesPartialState new_attributes = 2;
+  optional ItemAttributesPartialState old_attributes = 3;
+  optional ListChecksum list_checksum = 4;
+  optional ListChecksum old_attributes_checksum = 5;
+}
+message UpdateListAttributes {
+  required ListAttributesPartialState new_attributes = 1;
+  optional ListAttributesPartialState old_attributes = 2;
+  optional ListChecksum list_checksum = 3;
+  optional ListChecksum old_attributes_checksum = 4;
+}
+message Op {
+  enum Kind {
+    KIND_UNKNOWN = 0;
+    ADD = 2;
+    REM = 3;
+    MOV = 4;
+    UPDATE_ITEM_ATTRIBUTES = 5;
+    UPDATE_LIST_ATTRIBUTES = 6;
+  };
+  required Kind kind = 1;
+  optional Add add = 2;
+  optional Rem rem = 3;
+  optional Mov mov = 4;
+  optional UpdateItemAttributes update_item_attributes = 5;
+  optional UpdateListAttributes update_list_attributes = 6;
+}
+
+message OpList {
+  repeated Op ops = 1;
+}

+ 118 - 0
protocol/proto/playlist4service.proto

@@ -0,0 +1,118 @@
+import "playlist4changes.proto";
+import "playlist4content.proto";
+
+message RequestContext {
+  optional bool administrative = 2;
+  optional bool migration = 4;
+  optional string tag = 7;
+  optional bool useStarredView = 8;
+  optional bool syncWithPublished = 9;
+}
+message GetCurrentRevisionArgs {
+  optional bytes uri = 1;
+  optional RequestContext context = 2;
+}
+message GetChangesInSequenceRangeArgs {
+  optional bytes uri = 1;
+  optional RequestContext context = 2;
+  optional int32 fromSequenceNumber = 3;
+  optional int32 toSequenceNumber = 4;
+}
+message GetChangesInSequenceRangeMatchingPl3VersionArgs {
+  optional bytes uri = 1;
+  optional RequestContext context = 2;
+  optional int32 fromSequenceNumber = 3;
+  optional int32 toSequenceNumber = 4;
+  optional string pl3Version = 5;
+}
+message GetChangesInSequenceRangeReturn {
+  repeated RevisionTaggedChangeSet result = 1;
+}
+message ObliterateListArgs {
+  optional bytes uri = 1;
+  optional RequestContext context = 2;
+}
+message UpdatePublishedArgs {
+  optional bytes publishedUri = 1;
+  optional RequestContext context = 2;
+  optional bytes uri = 3;
+  optional bool isPublished = 4;
+}
+message SynchronizeArgs {
+  optional bytes uri = 1;
+  optional RequestContext context = 2;
+  optional ListContentSelection selection = 3;
+  optional ListChanges changes = 4;
+}
+message GetSnapshotAtRevisionArgs {
+  optional bytes uri = 1;
+  optional RequestContext context = 2;
+  optional bytes revision = 3;
+}
+message SubscribeRequest {
+  repeated bytes uris = 1;
+}
+message UnsubscribeRequest {
+  repeated bytes uris = 1;
+}
+enum Playlist4InboxErrorKind {
+  INBOX_NOT_ALLOWED = 2;
+  INBOX_INVALID_USER = 3;
+  INBOX_INVALID_URI = 4;
+  INBOX_LIST_TOO_LONG = 5;
+}
+message Playlist4ServiceException {
+  optional string why = 1;
+  optional string symbol = 2;
+  optional bool permanent = 3;
+  optional string serviceErrorClass = 4;
+  optional Playlist4InboxErrorKind inboxErrorKind = 5;
+}
+message SynchronizeReturn {
+  optional SelectedListContent result = 1;
+  optional Playlist4ServiceException exception = 4;
+}
+enum Playlist4ServiceMethodKind {
+  METHOD_UNKNOWN = 0;
+  METHOD_GET_CURRENT_REVISION = 2;
+  METHOD_GET_CHANGES_IN_SEQUENCE_RANGE = 3;
+  METHOD_OBLITERATE_LIST = 4;
+  METHOD_SYNCHRONIZE = 5;
+  METHOD_UPDATE_PUBLISHED = 6;
+  METHOD_GET_CHANGES_IN_SEQUENCE_RANGE_MATCHING_PL3_VERSION = 7;
+  METHOD_GET_SNAPSHOT_AT_REVISION = 8;
+}
+message Playlist4ServiceCall {
+  optional Playlist4ServiceMethodKind kind = 1;
+  optional GetCurrentRevisionArgs getCurrentRevisionArgs = 2;
+  optional GetChangesInSequenceRangeArgs getChangesInSequenceRangeArgs = 3;
+  optional ObliterateListArgs obliterateListArgs = 4;
+  optional SynchronizeArgs synchronizeArgs = 5;
+  optional UpdatePublishedArgs updatePublishedArgs = 6;
+  optional GetChangesInSequenceRangeMatchingPl3VersionArgs getChangesInSequenceRangeMatchingPl3VersionArgs = 7;
+  optional GetSnapshotAtRevisionArgs getSnapshotAtRevisionArgs = 8;
+}
+message Playlist4ServiceReturn {
+  optional Playlist4ServiceMethodKind kind = 1;
+  optional Playlist4ServiceException exception = 2;
+  optional bytes getCurrentRevisionReturn = 3;
+  optional GetChangesInSequenceRangeReturn getChangesInSequenceRangeReturn = 4;
+  optional bool obliterateListReturn = 5;
+  optional SynchronizeReturn synchronizeReturn = 6;
+  optional bool updatePublishedReturn = 7;
+  optional GetChangesInSequenceRangeReturn getChangesInSequenceRangeMatchingPl3VersionReturn = 8;
+  //optional RevisionTaggedListSnapshot getSnapshotAtRevisionReturn = 9;
+  optional bytes getSnapshotAtRevisionReturn = 9;
+}
+message CreateListReply {
+  required bytes uri = 1;
+  optional bytes revision = 2;
+}
+message ModifyReply {
+  required bytes uri = 1;
+  optional bytes revision = 2;
+}
+message PlaylistModificationInfo {
+  optional bytes uri = 1;
+  optional bytes new_revision = 2;
+}

+ 9 - 0
protocol/src/lib.rs

@@ -5,4 +5,13 @@ extern crate protobuf;
 
 mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
 mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));
+mod_path! mercury (concat!(env!("OUT_DIR"), "/mercury.rs"));
+mod_path! metadata (concat!(env!("OUT_DIR"), "/metadata.rs"));
+
+mod_path! playlist4changes (concat!(env!("OUT_DIR"), "/playlist4changes.rs"));
+mod_path! playlist4content (concat!(env!("OUT_DIR"), "/playlist4content.rs"));
+mod_path! playlist4issues (concat!(env!("OUT_DIR"), "/playlist4issues.rs"));
+mod_path! playlist4meta (concat!(env!("OUT_DIR"), "/playlist4meta.rs"));
+mod_path! playlist4ops (concat!(env!("OUT_DIR"), "/playlist4ops.rs"));
+mod_path! playlist4service (concat!(env!("OUT_DIR"), "/playlist4service.rs"));
 

+ 54 - 0
src/audio_decrypt.rs

@@ -0,0 +1,54 @@
+use crypto::aes;
+use crypto::symmetriccipher::SynchronousStreamCipher;
+use readall::ReadAllExt;
+use std::io;
+
+use audio_key::AudioKey;
+
+const AUDIO_AESIV : &'static [u8] = &[
+    0x72,0xe0,0x67,0xfb,0xdd,0xcb,0xcf,0x77,0xeb,0xe8,0xbc,0x64,0x3f,0x63,0x0d,0x93,
+];
+
+pub struct AudioDecrypt<T : io::Read> {
+    cipher: Box<SynchronousStreamCipher + 'static>,
+    key: AudioKey,
+    reader: T,
+}
+
+impl <T : io::Read> AudioDecrypt<T> {
+    pub fn new(key: AudioKey, mut reader: T) -> AudioDecrypt<T> {
+        let mut cipher = aes::ctr(aes::KeySize::KeySize128,
+                              &key,
+                              AUDIO_AESIV);
+
+        let mut buf = [0; 0xa7];
+        let mut buf2 = [0; 0xa7];
+        reader.read_all(&mut buf).unwrap();
+        cipher.process(&buf, &mut buf2);
+
+        AudioDecrypt {
+            cipher: cipher,
+            key: key,
+            reader: reader,
+        }
+    }
+}
+
+impl <T : io::Read> io::Read for AudioDecrypt<T> {
+    fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
+        let mut buffer = vec![0u8; output.len()];
+        let len = try!(self.reader.read(&mut buffer));
+
+        self.cipher.process(&buffer[..len], &mut output[..len]);
+
+        Ok(len)
+    }
+}
+
+impl <T : io::Read> io::Seek for AudioDecrypt<T> {
+    fn seek(&mut self, _pos: io::SeekFrom) -> io::Result<u64> {
+        Err(io::Error::new(io::ErrorKind::Other, "Cannot seek"))
+    }
+}
+
+

+ 149 - 0
src/audio_file.rs

@@ -0,0 +1,149 @@
+use byteorder::{ByteOrder, BigEndian};
+use std::cmp::min;
+use std::collections::BitSet;
+use std::io;
+use std::slice::bytes::copy_memory;
+use std::sync::{mpsc, Arc, Condvar, Mutex};
+
+use stream::{StreamRequest, StreamEvent};
+use util::FileId;
+
+const CHUNK_SIZE: usize = 0x40000;
+#[derive(Clone)]
+pub struct AudioFileRef(Arc<AudioFile>);
+
+struct AudioFile {
+    file: FileId,
+    size: usize,
+
+    data: Mutex<AudioFileData>,
+    cond: Condvar
+}
+
+struct AudioFileData {
+    buffer: Vec<u8>,
+    bitmap: BitSet,
+}
+
+impl AudioFileRef {
+    pub fn new(file: FileId, streams: mpsc::Sender<StreamRequest>) -> AudioFileRef {
+        let (tx, rx) = mpsc::channel();
+
+        streams.send(StreamRequest {
+            id: file,
+            offset: 0,
+            size: 1,
+            callback: tx
+        }).unwrap();
+
+        let size = {
+            let mut size = None;
+            for event in rx.iter() {
+                match event {
+                    StreamEvent::Header(id, data) => {
+                        if id == 0x3 {
+                            size = Some(BigEndian::read_u32(&data) * 4);
+                            break;
+                        }
+                    },
+                    StreamEvent::Data(_) => break
+                }
+            }
+            size.unwrap() as usize
+        };
+        
+        AudioFileRef(Arc::new(AudioFile {
+            file: file,
+            size: size,
+
+            data: Mutex::new(AudioFileData {
+                buffer: vec![0u8; size + (CHUNK_SIZE - size % CHUNK_SIZE)],
+                bitmap: BitSet::with_capacity(size / CHUNK_SIZE)
+            }),
+            cond: Condvar::new(),
+        }))
+    }
+    
+    pub fn fetch(&self, streams: mpsc::Sender<StreamRequest>) {
+        let &AudioFileRef(ref inner) = self;
+
+        let mut index : usize = 0;
+
+        while index * CHUNK_SIZE < inner.size {
+            let (tx, rx) = mpsc::channel();
+
+            streams.send(StreamRequest {
+                id: inner.file,
+                offset: (index * CHUNK_SIZE / 4) as u32,
+                size: (CHUNK_SIZE / 4) as u32,
+                callback: tx
+            }).unwrap();
+
+            let mut offset = 0;
+            for event in rx.iter() {
+                match event {
+                    StreamEvent::Header(_,_) => (),
+                    StreamEvent::Data(data) => {
+                        let mut handle = inner.data.lock().unwrap();
+                        copy_memory(&data, &mut handle.buffer[index * CHUNK_SIZE + offset..]);
+                        offset += data.len();
+
+                        if offset >= CHUNK_SIZE {
+                            break
+                        }
+                    }
+                }
+            }
+            
+            {
+                let mut handle = inner.data.lock().unwrap();
+                handle.bitmap.insert(index);
+                inner.cond.notify_all();
+            }
+
+            index += 1;
+        }
+    }
+}
+
+pub struct AudioFileReader {
+    file: AudioFileRef,
+    position: usize
+}
+
+impl AudioFileReader {
+    pub fn new(file: &AudioFileRef) -> AudioFileReader {
+        AudioFileReader {
+            file: file.clone(),
+            position: 0
+        }
+    }
+}
+
+impl io::Read for AudioFileReader {
+    fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
+        let index = self.position / CHUNK_SIZE;
+        let offset = self.position % CHUNK_SIZE;
+        let len = min(output.len(), CHUNK_SIZE-offset);
+
+        let &AudioFileRef(ref inner) = &self.file;
+        let mut handle = inner.data.lock().unwrap();
+
+        while !handle.bitmap.contains(&index) {
+            handle = inner.cond.wait(handle).unwrap();
+        }
+
+        copy_memory(&handle.buffer[self.position..self.position+len], output);
+        self.position += len;
+
+        Ok(len)
+    }
+}
+
+impl io::Seek for AudioFileReader {
+    fn seek(&mut self, _pos: io::SeekFrom) -> io::Result<u64> {
+        Err(io::Error::new(io::ErrorKind::Other, "Cannot seek"))
+    }
+}
+
+

+ 109 - 0
src/audio_key.rs

@@ -0,0 +1,109 @@
+use std::collections::HashMap;
+use std::sync::mpsc;
+use std::io::{Cursor, Write};
+use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
+use readall::ReadAllExt;
+
+use connection::Packet;
+use util::{SpotifyId, FileId};
+use util::Either::{Left, Right};
+use subsystem::Subsystem;
+
+pub struct AudioKeyRequest {
+    pub track: SpotifyId,
+    pub file: FileId,
+    pub callback: AudioKeyCallback,
+}
+pub type AudioKey = [u8; 16];
+pub struct AudioKeyResponse(pub AudioKey);
+pub type AudioKeyCallback = mpsc::Sender<AudioKeyResponse>;
+
+type AudioKeyId = u32;
+pub struct AudioKeyManager {
+    next_seq: AudioKeyId,
+    callbacks: HashMap<AudioKeyId, AudioKeyCallback>,
+
+    requests: mpsc::Receiver<AudioKeyRequest>,
+    packet_rx: mpsc::Receiver<Packet>,
+    packet_tx: mpsc::Sender<Packet>,
+}
+
+impl AudioKeyManager {
+    pub fn new(tx: mpsc::Sender<Packet>) -> (AudioKeyManager,
+                                             mpsc::Sender<AudioKeyRequest>,
+                                             mpsc::Sender<Packet>) {
+        let (req_tx, req_rx) = mpsc::channel();
+        let (pkt_tx, pkt_rx) = mpsc::channel();
+
+        (AudioKeyManager {
+            next_seq: 1,
+            callbacks: HashMap::new(),
+
+            requests: req_rx,
+            packet_rx: pkt_rx,
+            packet_tx: tx
+        }, req_tx, pkt_tx)
+    }
+
+    fn request(&mut self, req: AudioKeyRequest) {
+        let seq = self.next_seq;
+        self.next_seq += 1;
+
+        let mut data : Vec<u8> = Vec::new();
+        data.write(&req.file).unwrap();
+        data.write(&req.track.to_raw()).unwrap();
+        data.write_u32::<BigEndian>(seq).unwrap();
+        data.write_u16::<BigEndian>(0x0000).unwrap();
+
+        self.packet_tx.send(Packet {
+            cmd: 0xc,
+            data: data
+        }).unwrap();
+
+        self.callbacks.insert(seq, req.callback);
+    }
+
+    fn packet(&mut self, packet: Packet) {
+        assert_eq!(packet.cmd, 0xd);
+
+        let mut data = Cursor::new(&packet.data as &[u8]);
+        let seq = data.read_u32::<BigEndian>().unwrap();
+        let mut key = [0u8; 16];
+        data.read_all(&mut key).unwrap();
+
+        match self.callbacks.remove(&seq) {
+            Some(callback) => callback.send(AudioKeyResponse(key)).unwrap(),
+            None => ()
+        };
+    }
+}
+
+
+impl Subsystem for AudioKeyManager {
+    fn run(mut self) {
+        loop {
+            match {
+                let requests = &self.requests;
+                let packets = &self.packet_rx;
+
+                select!{
+                    r = requests.recv() => {
+                        Left(r.unwrap())
+                    },
+                    p = packets.recv() => {
+                        Right(p.unwrap())
+                    }
+                }
+            } {
+                Left(req) => {
+                    self.request(req);
+                }
+                Right(pkt) => {
+                    self.packet(pkt);
+                }
+            }
+
+        }
+    }
+}
+

+ 71 - 4
src/connection.rs

@@ -1,7 +1,4 @@
-use util;
-
-use byteorder::{self, ReadBytesExt, WriteBytesExt, BigEndian, ByteOrder};
-use keys::SharedKeys;
+use byteorder::{self, BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
 use readall::ReadAllExt;
 use shannon::ShannonStream;
 use std::convert;
@@ -9,6 +6,10 @@ use std::io;
 use std::io::Write;
 use std::net::TcpStream;
 use std::result;
+use std::sync::mpsc;
+
+use keys::SharedKeys;
+use util;
 
 #[derive(Debug)]
 pub enum Error {
@@ -37,6 +38,7 @@ pub struct PlainConnection {
     stream: TcpStream
 }
 
+#[derive(Clone)]
 pub struct CipherConnection {
     stream: ShannonStream<TcpStream>,
 }
@@ -106,5 +108,70 @@ impl CipherConnection {
     }
 }
 
+pub struct Packet {
+    pub cmd: u8,
+    pub data: Vec<u8>
+}
 
+pub struct SendThread {
+    connection: CipherConnection,
+    receiver: mpsc::Receiver<Packet>,
+}
+impl SendThread {
+    pub fn new(connection: CipherConnection)
+      -> (SendThread, mpsc::Sender<Packet>) {
+        let (tx, rx) = mpsc::channel();
+        (SendThread {
+            connection: connection,
+            receiver: rx
+        }, tx)
+    }
+
+    pub fn run(mut self) {
+        for req in self.receiver {
+            self.connection.send_encrypted_packet(
+                req.cmd, &req.data).unwrap();
+        }
+    }
+}
+
+pub struct PacketDispatch {
+    pub main: mpsc::Sender<Packet>,
+    pub stream: mpsc::Sender<Packet>,
+    pub mercury: mpsc::Sender<Packet>,
+    pub audio_key: mpsc::Sender<Packet>,
+}
+
+pub struct RecvThread {
+    connection: CipherConnection,
+    dispatch: PacketDispatch
+}
+
+impl RecvThread {
+    pub fn new(connection: CipherConnection, dispatch: PacketDispatch)
+        -> RecvThread {
+        RecvThread {
+            connection: connection,
+            dispatch: dispatch
+        }
+    }
+
+    pub fn run(mut self) {
+        loop {
+            let (cmd, data) = self.connection.recv_packet().unwrap();
+            let packet = Packet {
+                cmd: cmd,
+                data: data
+            };
+
+            match packet.cmd {
+                0x09 => &self.dispatch.stream,
+                0xd | 0xe => &self.dispatch.audio_key,
+                0xb2...0xb6 => &self.dispatch.mercury,
+                _ => &self.dispatch.main,
+            }.send(packet).unwrap();
+
+        }
+    }
+}
 

+ 8 - 6
src/keys.rs

@@ -1,8 +1,8 @@
-use rand;
-use gmp::Mpz;
-use num::FromPrimitive;
 use crypto;
 use crypto::mac::Mac;
+use gmp::Mpz;
+use num::FromPrimitive;
+use rand;
 use std::io::Write;
 
 use util;
@@ -29,7 +29,7 @@ pub struct PrivateKeys {
 }
 
 pub struct SharedKeys {
-    private: PrivateKeys,
+    //private: PrivateKeys,
     challenge: Vec<u8>,
     send_key: Vec<u8>,
     recv_key: Vec<u8>
@@ -51,9 +51,11 @@ impl PrivateKeys {
         }
     }
 
+    /*
     pub fn private_key(&self) -> Vec<u8> {
         return self.private_key.to_bytes_be();
     }
+    */
 
     pub fn public_key(&self) -> Vec<u8> {
         return self.public_key.to_bytes_be();
@@ -78,7 +80,7 @@ impl PrivateKeys {
         mac.input(server_packet);
 
         SharedKeys {
-            private: self,
+            //private: self,
             challenge: mac.result().code().to_vec(),
             send_key: data[0x14..0x34].to_vec(),
             recv_key: data[0x34..0x54].to_vec(),
@@ -94,7 +96,7 @@ impl SharedKeys {
     pub fn send_key(&self) -> &[u8] {
         &self.send_key
     }
-    
+
     pub fn recv_key(&self) -> &[u8] {
         &self.recv_key
     }

+ 53 - 6
src/main.rs

@@ -1,6 +1,6 @@
 #![crate_name = "librespot"]
 
-#![feature(plugin)]
+#![feature(alloc,plugin,core,collections,std_misc,zero_one)]
 
 #![plugin(protobuf_macros)]
 #[macro_use] extern crate lazy_static;
@@ -9,29 +9,45 @@ extern crate byteorder;
 extern crate crypto;
 extern crate gmp;
 extern crate num;
+extern crate portaudio;
 extern crate protobuf;
 extern crate shannon;
 extern crate rand;
 extern crate readall;
+extern crate vorbis;
 
 extern crate librespot_protocol;
 
+#[macro_use] mod util;
+mod audio_decrypt;
+mod audio_file;
+mod audio_key;
 mod connection;
 mod keys;
+mod mercury;
+mod metadata;
+mod player;
 mod session;
-mod util;
+mod stream;
+mod subsystem;
 
+use std::clone::Clone;
 use std::fs::File;
-use std::io::Read;
+use std::io::{Read, Write};
 use std::path::Path;
 
-use session::{Session,Config};
+use metadata::{MetadataCache, AlbumRef, ArtistRef, TrackRef};
+use session::{Config, Session};
+use util::SpotifyId;
+use player::Player;
 
 fn main() {
     let mut args = std::env::args().skip(1);
     let mut appkey_file = File::open(Path::new(&args.next().unwrap())).unwrap();
     let username = args.next().unwrap();
     let password = args.next().unwrap();
+    let track_uri = args.next().unwrap();
+    let track_id = SpotifyId::from_base62(track_uri.split(':').nth(2).unwrap());
 
     let mut appkey = Vec::new();
     appkey_file.read_to_end(&mut appkey).unwrap();
@@ -41,8 +57,39 @@ fn main() {
         user_agent: "ABC".to_string(),
         device_id: "ABC".to_string()
     };
-    let mut s = Session::new(config);
+    let session = Session::new(config);
+    session.login(username, password);
+    session.poll();
 
-    s.login(username, password);
+    let mut cache = MetadataCache::new(session.metadata.clone());
+    let track : TrackRef = cache.get(track_id);
+
+    let album : AlbumRef = {
+        let handle = track.wait();
+        let data = handle.unwrap();
+        eprintln!("{}", data.name);
+        cache.get(data.album)
+    };
+
+    let artists : Vec<ArtistRef> = {
+        let handle = album.wait();
+        let data = handle.unwrap();
+        eprintln!("{}", data.name);
+        data.artists.iter().map(|id| {
+            cache.get(*id)
+        }).collect()
+    };
+
+    for artist in artists {
+        let handle = artist.wait();
+        let data = handle.unwrap();
+        eprintln!("{}", data.name);
+    }
+    
+    Player::play(&session, track);
+
+    loop {
+        session.poll();
+    }
 }
 

+ 214 - 0
src/mercury.rs

@@ -0,0 +1,214 @@
+use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
+use protobuf::{self, Message};
+use readall::ReadAllExt;
+use std::collections::{HashMap, LinkedList};
+use std::io::{Cursor, Read, Write};
+use std::fmt;
+use std::mem::replace;
+use std::sync::mpsc;
+
+use connection::Packet;
+use librespot_protocol as protocol;
+use subsystem::Subsystem;
+use util::Either::{Left, Right};
+
+pub enum MercuryMethod {
+    GET,
+    GETX,
+    SUB,
+    UNSUB,
+}
+
+pub struct MercuryRequest {
+    pub method: MercuryMethod,
+    pub url: String,
+    pub mime: Option<String>,
+    pub callback: MercuryCallback
+}
+
+#[derive(Debug)]
+pub struct MercuryResponse {
+    pub url: String,
+    pub payload: LinkedList<Vec<u8>>
+}
+
+pub type MercuryCallback = Option<mpsc::Sender<MercuryResponse>>;
+
+pub struct MercuryPending {
+    parts: LinkedList<Vec<u8>>,
+    partial: Option<Vec<u8>>,
+    callback: MercuryCallback,
+}
+
+pub struct MercuryManager {
+    next_seq: u32,
+    pending: HashMap<Vec<u8>, MercuryPending>,
+
+    requests: mpsc::Receiver<MercuryRequest>,
+    packet_tx: mpsc::Sender<Packet>,
+    packet_rx: mpsc::Receiver<Packet>,
+}
+
+impl fmt::Display for MercuryMethod {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        formatter.write_str(match *self {
+            MercuryMethod::GET => "GET",
+            MercuryMethod::GETX => "GETX",
+            MercuryMethod::SUB => "SUB",
+            MercuryMethod::UNSUB => "UNSUB"
+        })
+    }
+}
+
+impl MercuryManager {
+    pub fn new(tx: mpsc::Sender<Packet>) -> (MercuryManager,
+                                             mpsc::Sender<MercuryRequest>,
+                                             mpsc::Sender<Packet>) {
+        let (req_tx, req_rx) = mpsc::channel();
+        let (pkt_tx, pkt_rx) = mpsc::channel();
+
+        (MercuryManager {
+            next_seq: 0,
+            pending: HashMap::new(),
+
+            requests: req_rx,
+            packet_rx: pkt_rx,
+            packet_tx: tx,
+        }, req_tx, pkt_tx)
+    }
+
+    fn request(&mut self, req: MercuryRequest) {
+        let mut seq = [0u8; 4];
+        BigEndian::write_u32(&mut seq, self.next_seq);
+        self.next_seq += 1;
+        let data = self.encode_request(&seq, &req);
+
+        self.packet_tx.send(Packet {
+            cmd: 0xb2,
+            data: data
+        }).unwrap();
+
+        self.pending.insert(seq.to_vec(), MercuryPending{
+            parts: LinkedList::new(),
+            partial: None,
+            callback: req.callback,
+        });
+    }
+
+    fn parse_part(mut s: &mut Read) -> Vec<u8> {
+        let size = s.read_u16::<BigEndian>().unwrap() as usize;
+        let mut buffer = vec![0; size];
+        s.read_all(&mut buffer).unwrap();
+
+        buffer
+    }
+
+    fn complete_request(&mut self, seq: &[u8]) {
+        let mut pending = self.pending.remove(seq).unwrap();
+
+        let header_data = match pending.parts.pop_front() {
+            Some(data) => data,
+            None => panic!("No header part !")
+        };
+
+        let header : protocol::mercury::MercuryReply =
+            protobuf::parse_from_bytes(&header_data).unwrap();
+
+        match pending.callback {
+            Some(ch) => {
+                ch.send(MercuryResponse{
+                    url: header.get_url().to_string(),
+                    payload: pending.parts
+                }).unwrap();
+            }
+            None => (),
+        }
+    }
+
+    fn handle_packet(&mut self, _cmd: u8, data: Vec<u8>) {
+        let mut packet = Cursor::new(data);
+
+        let seq = {
+            let seq_length = packet.read_u16::<BigEndian>().unwrap() as usize;
+            let mut seq = vec![0; seq_length];
+            packet.read_all(&mut seq).unwrap();
+            seq
+        };
+        let flags = packet.read_u8().unwrap();
+        let count = packet.read_u16::<BigEndian>().unwrap() as usize;
+
+        {
+            let pending : &mut MercuryPending = match self.pending.get_mut(&seq) {
+                Some(pending) => pending,
+                None => { return; }
+            };
+
+            for i in 0..count {
+                let mut part = Self::parse_part(&mut packet);
+                if pending.partial.is_some() {
+                    let mut data = replace(&mut pending.partial, None).unwrap();
+                    data.append(&mut part);
+                    part = data;
+                }
+
+                if i == count -1 && (flags == 2) {
+                    pending.partial = Some(part)
+                } else {
+                    pending.parts.push_back(part);
+                }
+            }
+        }
+
+        if flags == 0x1 {
+            self.complete_request(&seq);
+        }
+    }
+
+    fn encode_request(&self, seq: &[u8], req: &MercuryRequest) -> Vec<u8> {
+        let mut packet = Vec::new();
+        packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
+        packet.write_all(seq).unwrap();
+        packet.write_u8(1).unwrap(); // Flags: FINAL
+        packet.write_u16::<BigEndian>(1).unwrap(); // Part count. Only header
+
+        let mut header = protobuf_init!(protocol::mercury::MercuryRequest::new(), {
+            url: req.url.clone(),
+            method: req.method.to_string(),
+        });
+        req.mime.clone().map(|mime| header.set_mime(mime));
+
+        packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
+        header.write_to_writer(&mut packet).unwrap();
+
+        packet
+    }
+}
+
+impl Subsystem for MercuryManager {
+    fn run(mut self) {
+        loop {
+            match {
+                let requests = &self.requests;
+                let packets = &self.packet_rx;
+
+                select!{
+                    r = requests.recv() => {
+                        Left(r.unwrap())
+                    },
+                    p = packets.recv() => {
+                        Right(p.unwrap())
+                    }
+                }
+            } {
+                Left(req) => {
+                    self.request(req);
+                }
+                Right(pkt) => {
+                    self.handle_packet(pkt.cmd, pkt.data);
+                }
+            }
+
+        }
+    }
+}
+

+ 270 - 0
src/metadata.rs

@@ -0,0 +1,270 @@
+use protobuf::{self, Message};
+use std::any::{Any, TypeId};
+use std::collections::HashMap;
+use std::fmt;
+use std::slice::bytes::copy_memory;
+use std::sync::{mpsc, Arc, Condvar, Mutex, MutexGuard, Weak};
+use std::thread;
+
+use librespot_protocol as protocol;
+use mercury::{MercuryRequest, MercuryMethod};
+use subsystem::Subsystem;
+use util::{SpotifyId, FileId};
+
+pub trait MetadataTrait : Send + Any + 'static {
+    type Message: protobuf::MessageStatic;
+    fn from_msg(msg: &Self::Message) -> Self;
+    fn base_url() -> &'static str;
+    fn request(r: MetadataRef<Self>) -> MetadataRequest;
+}
+
+#[derive(Debug)]
+pub struct Track {
+    pub name: String,
+    pub album: SpotifyId,
+    pub files: Vec<FileId>
+}
+
+impl MetadataTrait for Track {
+    type Message = protocol::metadata::Track;
+    fn from_msg(msg: &Self::Message) -> Self {
+        Track {
+            name: msg.get_name().to_string(),
+            album: SpotifyId::from_raw(msg.get_album().get_gid()),
+            files: msg.get_file().iter()
+                .map(|file| {
+                    let mut dst = [0u8; 20];
+                    copy_memory(&file.get_gid(), &mut dst);
+                    dst
+                })
+                .collect(),
+        }
+    }
+    fn base_url() -> &'static str {
+        "hm://metadata/3/track"
+    }
+    fn request(r: MetadataRef<Self>) -> MetadataRequest {
+        MetadataRequest::Track(r)
+    }
+}
+
+#[derive(Debug)]
+pub struct Album {
+    pub name: String,
+    pub artists: Vec<SpotifyId>,
+    pub covers: Vec<FileId>
+}
+
+impl MetadataTrait for Album {
+    type Message = protocol::metadata::Album;
+    fn from_msg(msg: &Self::Message) -> Self {
+        Album {
+            name: msg.get_name().to_string(),
+            artists: msg.get_artist().iter()
+                .map(|a| SpotifyId::from_raw(a.get_gid()))
+                .collect(),
+            covers: msg.get_cover_group().get_image().iter()
+                .map(|image| {
+                    let mut dst = [0u8; 20];
+                    copy_memory(&image.get_file_id(), &mut dst);
+                    dst
+                })
+                .collect(),
+        }
+    }
+    fn base_url() -> &'static str {
+        "hm://metadata/3/album"
+    }
+    fn request(r: MetadataRef<Self>) -> MetadataRequest {
+        MetadataRequest::Album(r)
+    }
+}
+
+#[derive(Debug)]
+pub struct Artist {
+    pub name: String,
+}
+
+impl MetadataTrait for Artist {
+    type Message = protocol::metadata::Artist;
+    fn from_msg(msg: &Self::Message) -> Self {
+        Artist {
+            name: msg.get_name().to_string(),
+        }
+    }
+    fn base_url() -> &'static str {
+        "hm://metadata/3/artist"
+    }
+    fn request(r: MetadataRef<Self>) -> MetadataRequest {
+        MetadataRequest::Artist(r)
+    }
+}
+
+#[derive(Debug)]
+pub enum MetadataState<T> {
+    Loading,
+    Loaded(T),
+    Error,
+}
+
+pub struct Metadata<T: MetadataTrait> {
+    id: SpotifyId,
+    state: Mutex<MetadataState<T>>,
+    cond: Condvar
+}
+
+pub type MetadataRef<T> = Arc<Metadata<T>>;
+
+pub type TrackRef = MetadataRef<Track>;
+pub type AlbumRef = MetadataRef<Album>;
+pub type ArtistRef = MetadataRef<Artist>;
+
+pub struct MetadataCache {
+    metadata: mpsc::Sender<MetadataRequest>,
+    cache: HashMap<(SpotifyId, TypeId), Box<Any + 'static>>
+}
+
+impl MetadataCache {
+    pub fn new(metadata: mpsc::Sender<MetadataRequest>) -> MetadataCache {
+        MetadataCache {
+            metadata: metadata,
+            cache: HashMap::new()
+        }
+    }
+
+    pub fn get<T: MetadataTrait>(&mut self, id: SpotifyId)
+      -> MetadataRef<T> {
+        let key = (id, TypeId::of::<T>());
+
+        self.cache.get(&key)
+            .and_then(|x| x.downcast_ref::<Weak<Metadata<T>>>())
+            .and_then(|x| x.upgrade())
+            .unwrap_or_else(|| {
+                let x : MetadataRef<T> = Arc::new(Metadata{
+                    id: id,
+                    state: Mutex::new(MetadataState::Loading),
+                    cond: Condvar::new()
+                });
+
+                self.cache.insert(key, Box::new(x.downgrade()));
+                self.metadata.send(T::request(x.clone())).unwrap();
+                x
+            })
+    }
+}
+
+impl <T: MetadataTrait> Metadata<T> {
+    pub fn id(&self) -> SpotifyId {
+        self.id
+    }
+
+    pub fn lock(&self) -> MutexGuard<MetadataState<T>> {
+        self.state.lock().unwrap()
+    }
+
+    pub fn wait(&self) -> MutexGuard<MetadataState<T>> {
+        let mut handle = self.lock();
+        while handle.is_loading() {
+            handle = self.cond.wait(handle).unwrap();
+        }
+        handle
+    }
+
+    pub fn set(&self, state: MetadataState<T>) {
+        let mut handle = self.lock();
+        *handle = state;
+        self.cond.notify_all();
+    }
+}
+
+impl <T: MetadataTrait + fmt::Debug> fmt::Debug for Metadata<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        write!(f, "Metadata<>({:?}, {:?})", self.id, *self.lock())
+    }
+}
+
+impl <T: MetadataTrait> MetadataState<T> {
+    pub fn is_loading(&self) -> bool {
+        match *self {
+            MetadataState::Loading => true,
+            _ => false
+        }
+    }
+
+    pub fn is_loaded(&self) -> bool {
+        match *self {
+            MetadataState::Loaded(_) => true,
+            _ => false
+        }
+    }
+
+    pub fn unwrap<'s>(&'s self) -> &'s T {
+        match *self {
+            MetadataState::Loaded(ref data) => data,
+            _ => panic!("Not loaded")
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum MetadataRequest {
+    Artist(ArtistRef),
+    Album(AlbumRef),
+    Track(TrackRef)
+}
+
+pub struct MetadataManager {
+    requests: mpsc::Receiver<MetadataRequest>,
+    mercury: mpsc::Sender<MercuryRequest>
+}
+
+impl MetadataManager {
+    pub fn new(mercury: mpsc::Sender<MercuryRequest>) -> (MetadataManager,
+                                                          mpsc::Sender<MetadataRequest>) {
+        let (tx, rx) = mpsc::channel();
+        (MetadataManager {
+            requests: rx,
+            mercury: mercury
+        }, tx)
+    }
+
+    fn load<T: MetadataTrait> (&self, object: MetadataRef<T>) {
+        let mercury = self.mercury.clone();
+        thread::spawn(move || {
+            let (tx, rx) = mpsc::channel();
+            
+            mercury.send(MercuryRequest {
+                method: MercuryMethod::GET,
+                url: format!("{}/{}", T::base_url(), object.id.to_base16()),
+                mime: None,
+                callback: Some(tx)
+            }).unwrap();
+
+            let response = rx.recv().unwrap();
+
+            let msg : T::Message = protobuf::parse_from_bytes(
+                response.payload.front().unwrap()).unwrap();
+
+            object.set(MetadataState::Loaded(T::from_msg(&msg)));
+        });
+    }
+}
+
+impl Subsystem for MetadataManager {
+    fn run(self) {
+        for req in self.requests.iter() {
+            match req {
+                MetadataRequest::Artist(artist) => {
+                    self.load(artist)
+                }
+                MetadataRequest::Album(album) => {
+                    self.load(album)
+                }
+                MetadataRequest::Track(track) => {
+                    self.load(track)
+                }
+            }
+        }
+    }
+}
+

+ 73 - 0
src/player.rs

@@ -0,0 +1,73 @@
+use portaudio;
+use std::sync::mpsc;
+use std::thread;
+use vorbis;
+
+use audio_key::{AudioKeyRequest, AudioKeyResponse};
+use metadata::TrackRef;
+use session::Session;
+use audio_file::{AudioFileRef, AudioFileReader};
+use audio_decrypt::AudioDecrypt;
+
+pub struct Player;
+
+impl Player {
+    pub fn play(session: &Session, track: TrackRef) {
+        let file_id = *track.wait().unwrap().files.first().unwrap();
+
+        let key = {
+            let (tx, rx) = mpsc::channel();
+
+            session.audio_key.send(AudioKeyRequest {
+                track: track.id(),
+                file: file_id,
+                callback: tx
+            }).unwrap();
+            
+            let AudioKeyResponse(key) = rx.recv().unwrap();
+            key
+        };
+
+        let reader = {
+            let file = AudioFileRef::new(file_id, session.stream.clone());
+            let f = file.clone();
+            let s = session.stream.clone();
+            thread::spawn( move || { f.fetch(s) });
+            AudioDecrypt::new(key, AudioFileReader::new(&file))
+        };
+
+
+        portaudio::initialize().unwrap();
+
+        let stream = portaudio::stream::Stream::<i16>::open_default(
+                0,
+                2,
+                44100.0,
+                portaudio::stream::FRAMES_PER_BUFFER_UNSPECIFIED,
+                None
+                ).unwrap();
+        stream.start().unwrap();
+
+        let mut decoder = vorbis::Decoder::new(reader).unwrap();
+
+        for pkt in decoder.packets() {
+            match pkt {
+                Ok(packet) => {
+                    match stream.write(&packet.data) {
+                        Ok(_) => (),
+                        Err(portaudio::PaError::OutputUnderflowed)
+                            => eprintln!("Underflow"),
+                        Err(e) => panic!("PA Error {}", e)
+                    };
+                },
+                Err(vorbis::VorbisError::Hole) => (),
+                Err(e) => panic!("Vorbis error {:?}", e)
+            }
+        }
+
+        drop(stream);
+
+        portaudio::terminate().unwrap();
+    }
+}
+

+ 83 - 18
src/session.rs

@@ -1,13 +1,20 @@
-use connection::{PlainConnection, CipherConnection};
+use crypto::digest::Digest;
+use crypto::sha1::Sha1;
+use protobuf::{self, Message};
+use rand::thread_rng;
+use std::sync::mpsc;
+use std::thread;
+
+use audio_key;
+use connection::{PlainConnection, Packet, PacketDispatch, SendThread, RecvThread};
 use keys::PrivateKeys;
 use librespot_protocol as protocol;
+use mercury;
+use metadata;
+use stream;
+use subsystem::Subsystem;
 use util;
 
-use crypto::sha1::Sha1;
-use crypto::digest::Digest;
-use protobuf::*;
-use rand::thread_rng;
-
 pub struct Config {
     pub application_key: Vec<u8>,
     pub user_agent: String,
@@ -16,7 +23,14 @@ pub struct Config {
 
 pub struct Session {
     config: Config,
-    connection: CipherConnection,
+
+    packet_rx: mpsc::Receiver<Packet>,
+    pub packet_tx: mpsc::Sender<Packet>,
+
+    pub audio_key: mpsc::Sender<audio_key::AudioKeyRequest>,
+    pub mercury: mpsc::Sender<mercury::MercuryRequest>,
+    pub metadata: mpsc::Sender<metadata::MetadataRequest>,
+    pub stream: mpsc::Sender<stream::StreamRequest>,
 }
 
 impl Session {
@@ -27,7 +41,6 @@ impl Session {
             h.result_str()
         };
 
-
         let keys = PrivateKeys::new();
         let mut connection = PlainConnection::connect().unwrap();
 
@@ -68,7 +81,7 @@ impl Session {
             connection.recv_packet().unwrap();
 
         let response : protocol::keyexchange::APResponseMessage =
-            parse_from_bytes(&init_server_packet[4..]).unwrap();
+            protobuf::parse_from_bytes(&init_server_packet[4..]).unwrap();
 
         protobuf_bind!(response, {
             challenge => {
@@ -90,13 +103,47 @@ impl Session {
 
         connection.send_packet(&packet.write_to_bytes().unwrap()).unwrap();
 
+        let cipher_connection = connection.setup_cipher(shared_keys);
+
+        let (send_thread, tx) = SendThread::new(cipher_connection.clone());
+
+        let (main_tx, rx) = mpsc::channel();
+        let (mercury, mercury_req, mercury_pkt)
+            = mercury::MercuryManager::new(tx.clone());
+        let (metadata, metadata_req)
+            = metadata::MetadataManager::new(mercury_req.clone());
+        let (stream, stream_req, stream_pkt)
+            = stream::StreamManager::new(tx.clone());
+        let (audio_key, audio_key_req, audio_key_pkt)
+            = audio_key::AudioKeyManager::new(tx.clone());
+
+        let recv_thread = RecvThread::new(cipher_connection, PacketDispatch {
+            main: main_tx,
+            stream: stream_pkt,
+            mercury: mercury_pkt,
+            audio_key: audio_key_pkt
+        });
+
+        thread::spawn(move || send_thread.run());
+        thread::spawn(move || recv_thread.run());
+
+        mercury.start();
+        metadata.start();
+        stream.start();
+        audio_key.start();
+
         Session {
             config: config,
-            connection: connection.setup_cipher(shared_keys)
+            packet_rx: rx,
+            packet_tx: tx,
+            mercury: mercury_req,
+            metadata: metadata_req,
+            stream: stream_req,
+            audio_key: audio_key_req,
         }
     }
 
-    pub fn login(&mut self, username: String, password: String) {
+    pub fn login(&self, username: String, password: String) {
         let packet = protobuf_init!(protocol::authentication::ClientResponseEncrypted::new(), {
             login_credentials => {
                 username: username,
@@ -119,14 +166,32 @@ impl Session {
             }
         });
 
-        self.connection.send_encrypted_packet(
-            0xab,
-            &packet.write_to_bytes().unwrap()).unwrap();
+        self.packet_tx.send(Packet {
+            cmd: 0xab,
+            data: packet.write_to_bytes().unwrap()
+        }).unwrap();
+    }
 
-        loop {
-            let (cmd, data) = self.connection.recv_packet().unwrap();
-            println!("{:x}", cmd);
-        }
+    pub fn poll(&self) {
+        let packet = self.packet_rx.recv().unwrap();
+
+        match packet.cmd {
+            0x4 => { // PING
+                self.packet_tx.send(Packet {
+                    cmd: 0x49,
+                    data: packet.data
+                }).unwrap();
+            }
+            0x4a => { // PONG
+            }
+            0xac => { // AUTHENTICATED
+                eprintln!("Authentication succeedded");
+            }
+            0xad => {
+                eprintln!("Authentication failed");
+            }
+            _ => ()
+        };
     }
 }
 

+ 159 - 0
src/stream.rs

@@ -0,0 +1,159 @@
+use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
+use std::collections::HashMap;
+use std::io::{Cursor, Seek, SeekFrom, Write};
+use std::sync::mpsc;
+
+use connection::Packet;
+use util::{ArcVec, FileId};
+use util::Either::{Left, Right};
+use subsystem::Subsystem;
+
+pub type StreamCallback = mpsc::Sender<StreamEvent>;
+pub struct StreamRequest {
+    pub id: FileId,
+    pub offset: u32,
+    pub size: u32,
+    pub callback: StreamCallback
+}
+
+#[derive(Debug)]
+pub enum StreamEvent {
+    Header(u8, ArcVec<u8>),
+    Data(ArcVec<u8>),
+}
+
+type ChannelId = u16;
+
+enum ChannelMode {
+    Header,
+    Data
+}
+
+struct Channel {
+    mode: ChannelMode,
+    callback: StreamCallback
+}
+
+pub struct StreamManager {
+    next_id: ChannelId,
+    channels: HashMap<ChannelId, Channel>,
+
+    requests: mpsc::Receiver<StreamRequest>,
+    packet_rx: mpsc::Receiver<Packet>,
+    packet_tx: mpsc::Sender<Packet>,
+}
+
+impl StreamManager {
+    pub fn new(tx: mpsc::Sender<Packet>) -> (StreamManager,
+                                             mpsc::Sender<StreamRequest>,
+                                             mpsc::Sender<Packet>) {
+        let (req_tx, req_rx) = mpsc::channel();
+        let (pkt_tx, pkt_rx) = mpsc::channel();
+
+        (StreamManager {
+            next_id: 0,
+            channels: HashMap::new(),
+
+            requests: req_rx,
+            packet_rx: pkt_rx,
+            packet_tx: tx
+        }, req_tx, pkt_tx)
+    }
+
+    fn request(&mut self, req: StreamRequest) {
+        let channel_id = self.next_id;
+        self.next_id += 1;
+
+        let mut data : Vec<u8> = Vec::new();
+        data.write_u16::<BigEndian>(channel_id).unwrap();
+        data.write_u8(0).unwrap();
+        data.write_u8(1).unwrap();
+        data.write_u16::<BigEndian>(0x0000).unwrap();
+        data.write_u32::<BigEndian>(0x00000000).unwrap();
+        data.write_u32::<BigEndian>(0x00009C40).unwrap();
+        data.write_u32::<BigEndian>(0x00020000).unwrap();
+        data.write(&req.id).unwrap();
+        data.write_u32::<BigEndian>(req.offset).unwrap();
+        data.write_u32::<BigEndian>(req.offset + req.size).unwrap();
+
+        self.packet_tx.send(Packet {
+            cmd: 0x8,
+            data: data
+        }).unwrap();
+
+        self.channels.insert(channel_id, Channel {
+            mode: ChannelMode::Header,
+            callback: req.callback
+        });
+    }
+
+    fn packet(&mut self, data: Vec<u8>) {
+        let data = ArcVec::new(data);
+        let mut packet = Cursor::new(&data as &[u8]);
+
+        let id : ChannelId = packet.read_u16::<BigEndian>().unwrap();
+        let channel = match self.channels.get_mut(&id) {
+            Some(ch) => ch,
+            None => { return; }
+        };
+
+        match channel.mode {
+            ChannelMode::Header => {
+                let mut length = 0;
+
+                while packet.position() < data.len() as u64 {
+                    length = packet.read_u16::<BigEndian>().unwrap();
+                    if length > 0 {
+                        let header_id = packet.read_u8().unwrap();
+                        channel.callback.send(StreamEvent::Header(
+                                header_id,
+                                data.clone()
+                                    .offset(packet.position() as usize)
+                                    .limit(length as usize - 1)
+                            )).unwrap();
+
+                        packet.seek(SeekFrom::Current(length as i64 - 1)).unwrap();
+                    }
+                }
+                
+                if length == 0 {
+                    channel.mode = ChannelMode::Data;
+                }
+            }
+
+            ChannelMode::Data => {
+                if packet.position() < data.len() as u64 {
+                    channel.callback.send(StreamEvent::Data(
+                            data.clone().offset(packet.position() as usize))).unwrap();
+                } else {
+                    // TODO: close the channel
+                }
+            }
+        }
+    }
+}
+
+impl Subsystem for StreamManager {
+    fn run(mut self) {
+        loop {
+            match {
+                let requests = &self.requests;
+                let packets = &self.packet_rx;
+
+                select!{
+                    r = requests.recv() => {
+                        Left(r.unwrap())
+                    },
+                    p = packets.recv() => {
+                        Right(p.unwrap())
+                    }
+                }
+            } {
+                Left(req) => self.request(req),
+                Right(pkt) => self.packet(pkt.data)
+            }
+        }
+    }
+}
+
+

+ 9 - 0
src/subsystem.rs

@@ -0,0 +1,9 @@
+use std::thread;
+
+pub trait Subsystem : Send + Sized + 'static {
+    fn run(self);
+    fn start(self) {
+        thread::spawn(move || self.run());
+    }
+}
+

+ 0 - 29
src/util.rs

@@ -1,29 +0,0 @@
-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
-}
-
-pub mod version {
-    include!(concat!(env!("OUT_DIR"), "/version.rs"));
-
-    pub fn version_string() -> String {
-        format!("librespot-{}", short_sha())
-    }
-}
-

+ 52 - 0
src/util/arcvec.rs

@@ -0,0 +1,52 @@
+use std::sync::Arc;
+use std::fmt;
+use std::ops::Deref;
+
+#[derive(Clone)]
+pub struct ArcVec<T> {
+    data: Arc<Vec<T>>,
+    offset: usize,
+    length: usize,
+}
+
+impl <T> ArcVec<T> {
+    pub fn new(data: Vec<T>) -> ArcVec<T> {
+        let length = data.len();
+        ArcVec {
+            data: Arc::new(data),
+            offset: 0,
+            length: length
+        }
+    }
+
+    pub fn offset(mut self, offset: usize) -> ArcVec<T> {
+        assert!(offset <= self.length);
+
+        self.offset += offset;
+        self.length -= offset;
+
+        self
+    }
+
+    pub fn limit(mut self, length: usize) -> ArcVec<T> {
+        assert!(length <= self.length);
+        self.length = length;
+
+        self
+    }
+}
+
+impl<T> Deref for ArcVec<T> {
+    type Target = [T];
+
+    fn deref(&self) -> &[T] {
+        &self.data[self.offset..self.offset+self.length]
+    }
+}
+
+impl<T : fmt::Debug> fmt::Debug for ArcVec<T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        self.deref().fmt(formatter)
+    }
+}
+

+ 94 - 0
src/util/int128.rs

@@ -0,0 +1,94 @@
+use std;
+
+#[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)]
+#[allow(non_camel_case_types)]
+pub struct u128 {
+    high: u64,
+    low: u64
+}
+
+impl u128 {
+    pub fn from_parts(high: u64, low: u64) -> u128 {
+        u128 { high: high, low: low }
+    }
+
+    pub fn parts(&self) -> (u64, u64) {
+        (self.high, self.low)
+    }
+}
+
+impl std::num::Zero for u128 {
+    fn zero() -> u128 {
+        u128::from_parts(0, 0)
+    }
+}
+
+impl std::ops::Add<u128> for u128 {
+    type Output = u128;
+    fn add(self, rhs: u128) -> u128 {
+        let low = self.low + rhs.low;
+        let high = self.high + rhs.high +
+            if low < self.low { 1 } else { 0 };
+
+        u128::from_parts(high, low)
+    }
+}
+
+impl <'a> std::ops::Add<&'a u128> for u128 {
+    type Output = u128;
+    fn add(self, rhs: &'a u128) -> u128 {
+        let low = self.low + rhs.low;
+        let high = self.high + rhs.high +
+            if low < self.low { 1 } else { 0 };
+
+        u128::from_parts(high, low)
+    }
+}
+
+impl std::convert::From<u8> for u128 {
+    fn from(n: u8) -> u128 {
+        u128::from_parts(0, n as u64)
+    }
+}
+
+
+impl std::ops::Mul<u128> for u128 {
+    type Output = u128;
+
+    fn mul(self, rhs: u128) -> u128 {
+        let top: [u64; 4] =
+            [self.high >> 32, self.high & 0xFFFFFFFF,
+              self.low >> 32,  self.low & 0xFFFFFFFF];
+
+        let bottom : [u64; 4] =
+            [rhs.high >> 32, rhs.high & 0xFFFFFFFF,
+              rhs.low >> 32,  rhs.low & 0xFFFFFFFF];
+
+        let mut rows = [std::num::Zero::zero(); 16];
+        for i in 0..4 {
+            for j in 0..4 {
+                let shift = i + j;
+                let product = top[3-i] * bottom[3-j];
+                let (high, low) = match shift {
+                    0 => (0, product),
+                    1 => (product >> 32, product << 32),
+                    2 => (product, 0),
+                    3 => (product << 32, 0),
+                    _ => {
+                        if product != 0 {
+                            panic!("Overflow on mul {:?} {:?} ({} {})",
+                                self, rhs, i, j)
+                        } else {
+                            (0, 0)
+                        }
+                    }
+                };
+                rows[j * 4 + i] = u128::from_parts(high, low);
+            }
+        }
+
+        rows.iter().sum::<u128>()
+    }
+}
+
+

+ 68 - 0
src/util/mod.rs

@@ -0,0 +1,68 @@
+use rand::{Rng,Rand};
+
+mod int128;
+mod spotify_id;
+mod arcvec;
+
+pub use util::int128::u128;
+pub use util::spotify_id::{SpotifyId, FileId};
+pub use util::arcvec::ArcVec;
+
+#[macro_export]
+macro_rules! eprintln(
+    ($($arg:tt)*) => (
+        {
+            use std::io::Write;
+            writeln!(&mut ::std::io::stderr(), $($arg)* ).unwrap()
+        }
+    )
+);
+#[macro_export]
+macro_rules! eprint(
+    ($($arg:tt)*) => (
+        {
+            use std::io::Write;
+            write!(&mut ::std::io::stderr(), $($arg)* ).unwrap()
+        }
+    )
+);
+
+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
+}
+
+pub mod version {
+    include!(concat!(env!("OUT_DIR"), "/version.rs"));
+
+    pub fn version_string() -> String {
+        format!("librespot-{}", short_sha())
+    }
+}
+
+pub enum Either<S,T> {
+    Left(S),
+    Right(T)
+}
+
+pub fn hexdump(data: &[u8]) {
+    for b in data.iter() {
+        eprint!("{:02X} ", b);
+    }
+    eprintln!("");
+}
+

+ 79 - 0
src/util/spotify_id.rs

@@ -0,0 +1,79 @@
+use std;
+use util::u128;
+use byteorder::{BigEndian,ByteOrder};
+use std::ascii::AsciiExt;
+
+pub type FileId = [u8; 20];
+
+#[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)]
+pub struct SpotifyId(u128);
+
+const BASE62_DIGITS: &'static [u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
+
+impl SpotifyId {
+    pub fn from_base16(id: &str) -> SpotifyId {
+        assert!(id.is_ascii());
+        let data = id.as_bytes();
+
+        let mut n : u128 = std::num::Zero::zero();
+        for c in data {
+            let d = BASE16_DIGITS.position_elem(c).unwrap() as u8;
+            n = n * u128::from(16);
+            n = n + u128::from(d);
+        }
+
+        SpotifyId(n)
+    }
+
+    pub fn from_base62(id: &str) -> SpotifyId {
+        assert!(id.is_ascii());
+        let data = id.as_bytes();
+
+        let mut n : u128 = std::num::Zero::zero();
+        for c in data {
+            let d = BASE62_DIGITS.position_elem(c).unwrap() as u8;
+            n = n * u128::from(62);
+            n = n + u128::from(d);
+        }
+
+        SpotifyId(n)
+    }
+
+    pub fn from_raw(data: &[u8]) -> SpotifyId {
+        assert_eq!(data.len(), 16);
+
+        let high = BigEndian::read_u64(&data[0..8]);
+        let low = BigEndian::read_u64(&data[8..16]);
+
+        SpotifyId(u128::from_parts(high, low))
+    }
+
+    pub fn to_base16(&self) -> String {
+        let &SpotifyId(ref n) = self;
+        let (high, low) = n.parts();
+
+        let mut data = [0u8; 32];
+        for i in 0..16 {
+            data[31-i] = BASE16_DIGITS[(low.wrapping_shr(4 * i as u32) & 0xF) as usize];
+        }
+        for i in 0..16 {
+            data[15-i] = BASE16_DIGITS[(high.wrapping_shr(4 * i as u32) & 0xF) as usize];
+        }
+
+        std::str::from_utf8(&data).unwrap().to_string()
+    }
+
+    pub fn to_raw(&self) -> [u8; 16] {
+        let &SpotifyId(ref n) = self;
+        let (high, low) = n.parts();
+
+        let mut data = [0u8; 16];
+
+        BigEndian::write_u64(&mut data[0..8],  high);
+        BigEndian::write_u64(&mut data[8..16], low);
+
+        data
+    }
+}
+