mirror of
https://github.com/librespot-org/librespot.git
synced 2024-11-08 16:45:43 +00:00
Initial commit.
This commit is contained in:
commit
a993b60ffa
17 changed files with 872 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
|
.cargo
|
107
Cargo.lock
generated
Normal file
107
Cargo.lock
generated
Normal file
|
@ -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
Cargo.toml
Normal file
28
Cargo.toml
Normal file
|
@ -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
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -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
build.rs
Normal file
43
build.rs
Normal file
|
@ -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
protocol/authentication.proto
Normal file
51
protocol/authentication.proto
Normal file
|
@ -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
protocol/keyexchange.proto
Normal file
61
protocol/keyexchange.proto
Normal file
|
@ -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
protocol/mercury.proto
Normal file
49
protocol/mercury.proto
Normal file
|
@ -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
protocol/metadata.proto
Normal file
154
protocol/metadata.proto
Normal file
|
@ -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
protocol/spirc.proto
Normal file
92
protocol/spirc.proto
Normal file
|
@ -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
protocol/spotify.proto
Normal file
0
protocol/spotify.proto
Normal file
43
src/connection.rs
Normal file
43
src/connection.rs
Normal file
|
@ -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
src/cryptoutil.rs
Normal file
91
src/cryptoutil.rs
Normal file
|
@ -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
src/main.rs
Normal file
28
src/main.rs
Normal file
|
@ -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
src/protocol.rs
Normal file
3
src/protocol.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
|
||||||
|
mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));
|
||||||
|
|
78
src/session.rs
Normal file
78
src/session.rs
Normal file
|
@ -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
src/util.rs
Normal file
20
src/util.rs
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue